Split FlViewRenderer into OpenGL and software backends (#188824)
diff --git a/engine/src/flutter/shell/platform/linux/BUILD.gn b/engine/src/flutter/shell/platform/linux/BUILD.gn
index 66797b3..52f189e 100644
--- a/engine/src/flutter/shell/platform/linux/BUILD.gn
+++ b/engine/src/flutter/shell/platform/linux/BUILD.gn
@@ -116,7 +116,6 @@
"fl_basic_message_channel.cc",
"fl_binary_codec.cc",
"fl_binary_messenger.cc",
- "fl_compositor.cc",
"fl_compositor_opengl.cc",
"fl_compositor_opengl_shader.cc",
"fl_compositor_software.cc",
@@ -174,6 +173,8 @@
"fl_view_accessible.cc",
"fl_view_monitor.cc",
"fl_view_renderer.cc",
+ "fl_view_renderer_opengl.cc",
+ "fl_view_renderer_software.cc",
"fl_window_monitor.cc",
"fl_window_state_monitor.cc",
]
diff --git a/engine/src/flutter/shell/platform/linux/fl_compositor.cc b/engine/src/flutter/shell/platform/linux/fl_compositor.cc
deleted file mode 100644
index 28d2454..0000000
--- a/engine/src/flutter/shell/platform/linux/fl_compositor.cc
+++ /dev/null
@@ -1,37 +0,0 @@
-// 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.
-
-#include "fl_compositor.h"
-
-G_DEFINE_QUARK(fl_compositor_error_quark, fl_compositor_error)
-
-G_DEFINE_TYPE(FlCompositor, fl_compositor, G_TYPE_OBJECT)
-
-static void fl_compositor_class_init(FlCompositorClass* klass) {}
-
-static void fl_compositor_init(FlCompositor* self) {}
-
-gboolean fl_compositor_present_layers(FlCompositor* self,
- const FlutterLayer** layers,
- size_t layers_count) {
- g_return_val_if_fail(FL_IS_COMPOSITOR(self), FALSE);
- return FL_COMPOSITOR_GET_CLASS(self)->present_layers(self, layers,
- layers_count);
-}
-
-void fl_compositor_get_frame_size(FlCompositor* self,
- size_t* width,
- size_t* height) {
- g_return_if_fail(FL_IS_COMPOSITOR(self));
- return FL_COMPOSITOR_GET_CLASS(self)->get_frame_size(self, width, height);
-}
-
-gboolean fl_compositor_render(FlCompositor* self,
- cairo_t* cr,
- GdkWindow* window,
- gboolean wait_for_frame) {
- g_return_val_if_fail(FL_IS_COMPOSITOR(self), FALSE);
- return FL_COMPOSITOR_GET_CLASS(self)->render(self, cr, window,
- wait_for_frame);
-}
diff --git a/engine/src/flutter/shell/platform/linux/fl_compositor.h b/engine/src/flutter/shell/platform/linux/fl_compositor.h
deleted file mode 100644
index d25d67b..0000000
--- a/engine/src/flutter/shell/platform/linux/fl_compositor.h
+++ /dev/null
@@ -1,88 +0,0 @@
-// 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_FL_COMPOSITOR_H_
-#define FLUTTER_SHELL_PLATFORM_LINUX_FL_COMPOSITOR_H_
-
-#include <cairo.h>
-#include <gdk/gdk.h>
-
-#include "flutter/shell/platform/embedder/embedder.h"
-
-G_BEGIN_DECLS
-
-// Maximum time to wait for a frame to be ready before giving up and rendering.
-constexpr gint64 kCompositorRenderTimeoutMicroseconds = 100000; // 100ms
-
-G_DECLARE_DERIVABLE_TYPE(FlCompositor, fl_compositor, FL, COMPOSITOR, GObject)
-
-struct _FlCompositorClass {
- GObjectClass parent_class;
-
- gboolean (*present_layers)(FlCompositor* compositor,
- const FlutterLayer** layers,
- size_t layers_count);
-
- void (*get_frame_size)(FlCompositor* compositor,
- size_t* width,
- size_t* height);
-
- gboolean (*render)(FlCompositor* compositor,
- cairo_t* cr,
- GdkWindow* window,
- gboolean wait_for_frame);
-};
-
-/**
- * FlCompositor:
- *
- * #FlCompositor is an abstract class that implements Flutter compositing.
- */
-
-/**
- * fl_compositor_present_layers:
- * @compositor: an #FlCompositor.
- * @layers: layers to be composited.
- * @layers_count: number of layers.
- *
- * Composite layers. Called from the Flutter rendering thread.
- *
- * Returns %TRUE if successful.
- */
-gboolean fl_compositor_present_layers(FlCompositor* compositor,
- const FlutterLayer** layers,
- size_t layers_count);
-
-/**
- * fl_compositor_get_frame_size:
- * @compositor: an #FlCompositor.
- * @width: location to write frame width in pixels.
- * @height: location to write frame height in pixels.
- *
- * Get the size of the layer ready for rendering.
- */
-void fl_compositor_get_frame_size(FlCompositor* compositor,
- size_t* width,
- size_t* height);
-
-/**
- * fl_compositor_render:
- * @compositor: an #FlCompositor.
- * @cr: a Cairo rendering context.
- * @window: window being rendered into.
- * @wait_for_frame: if the available frame is not the size of the window block
- * until a new frame is received.
- *
- * Renders the current frame. Called from the GTK thread.
- *
- * Returns %TRUE if successful.
- */
-gboolean fl_compositor_render(FlCompositor* compositor,
- cairo_t* cr,
- GdkWindow* window,
- gboolean wait_for_frame);
-
-G_END_DECLS
-
-#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_COMPOSITOR_H_
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 ebcad24..f434889 100644
--- a/engine/src/flutter/shell/platform/linux/fl_compositor_opengl.cc
+++ b/engine/src/flutter/shell/platform/linux/fl_compositor_opengl.cc
@@ -14,10 +14,7 @@
#include "flutter/shell/platform/linux/fl_framebuffer.h"
struct _FlCompositorOpenGL {
- FlCompositor parent_instance;
-
- // Task runner to wait for frames on.
- FlTaskRunner* task_runner;
+ GObject parent_instance;
// TRUE if can share framebuffers between contexts.
gboolean shareable;
@@ -33,14 +30,39 @@
// Shader program used to composite layers.
FlCompositorOpenGLShader* shader;
-
- // Ensure Flutter and GTK can access the frame data (framebuffer or pixels).
- GMutex frame_mutex;
};
-G_DEFINE_TYPE(FlCompositorOpenGL,
- fl_compositor_opengl,
- fl_compositor_get_type())
+G_DEFINE_TYPE(FlCompositorOpenGL, fl_compositor_opengl, G_TYPE_OBJECT)
+
+static void fl_compositor_opengl_dispose(GObject* object) {
+ FlCompositorOpenGL* self = FL_COMPOSITOR_OPENGL(object);
+
+ g_clear_object(&self->shader);
+
+ g_clear_object(&self->opengl_manager);
+ g_clear_object(&self->framebuffer);
+ g_clear_pointer(&self->pixels, g_free);
+
+ G_OBJECT_CLASS(fl_compositor_opengl_parent_class)->dispose(object);
+}
+
+static void fl_compositor_opengl_class_init(FlCompositorOpenGLClass* klass) {
+ G_OBJECT_CLASS(klass)->dispose = fl_compositor_opengl_dispose;
+}
+
+static void fl_compositor_opengl_init(FlCompositorOpenGL* self) {}
+
+FlCompositorOpenGL* fl_compositor_opengl_new(FlOpenGLManager* opengl_manager,
+ gboolean shareable) {
+ FlCompositorOpenGL* self = FL_COMPOSITOR_OPENGL(
+ g_object_new(fl_compositor_opengl_get_type(), nullptr));
+
+ self->shareable = shareable;
+ self->opengl_manager = FL_OPENGL_MANAGER(g_object_ref(opengl_manager));
+ self->shader = fl_compositor_opengl_shader_new(opengl_manager);
+
+ return self;
+}
static void composite_layer(FlCompositorOpenGL* self,
FlFramebuffer* framebuffer,
@@ -61,14 +83,10 @@
glDrawArrays(GL_TRIANGLES, 0, 6);
}
-static gboolean fl_compositor_opengl_present_layers(FlCompositor* compositor,
- const FlutterLayer** layers,
- size_t layers_count) {
- FlCompositorOpenGL* self = FL_COMPOSITOR_OPENGL(compositor);
-
- g_mutex_lock(&self->frame_mutex);
+gboolean fl_compositor_opengl_composite_layers(FlCompositorOpenGL* self,
+ const FlutterLayer** layers,
+ size_t layers_count) {
if (layers_count == 0) {
- g_mutex_unlock(&self->frame_mutex);
return TRUE;
}
@@ -201,20 +219,12 @@
}
glBindFramebuffer(GL_READ_FRAMEBUFFER, saved_read_framebuffer_binding);
- g_mutex_unlock(&self->frame_mutex);
-
- fl_task_runner_stop_wait(self->task_runner);
-
return TRUE;
}
-static void fl_compositor_opengl_get_frame_size(FlCompositor* compositor,
- size_t* width,
- size_t* height) {
- FlCompositorOpenGL* self = FL_COMPOSITOR_OPENGL(compositor);
-
- g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&self->frame_mutex);
-
+void fl_compositor_opengl_get_frame_size(FlCompositorOpenGL* self,
+ size_t* width,
+ size_t* height) {
if (width != nullptr) {
*width = self->framebuffer != nullptr
? fl_framebuffer_get_width(self->framebuffer)
@@ -227,47 +237,16 @@
}
}
-static gboolean fl_compositor_opengl_render(FlCompositor* compositor,
- cairo_t* cr,
- GdkWindow* window,
- gboolean wait_for_frame) {
- FlCompositorOpenGL* self = FL_COMPOSITOR_OPENGL(compositor);
-
- g_mutex_lock(&self->frame_mutex);
+gboolean fl_compositor_opengl_render(FlCompositorOpenGL* self,
+ cairo_t* cr,
+ GdkWindow* window) {
if (self->framebuffer == nullptr) {
- g_mutex_unlock(&self->frame_mutex);
return FALSE;
}
- // If frame not ready, then wait for it.
gint scale_factor = gdk_window_get_scale_factor(window);
- size_t width, height;
- gint64 expiry_time =
- g_get_monotonic_time() + kCompositorRenderTimeoutMicroseconds;
- while (true) {
- width = gdk_window_get_width(window) * scale_factor;
- height = gdk_window_get_height(window) * scale_factor;
- if (!wait_for_frame) {
- break;
- }
-
- size_t framebuffer_width = fl_framebuffer_get_width(self->framebuffer);
- size_t framebuffer_height = fl_framebuffer_get_height(self->framebuffer);
- if (framebuffer_width == width && framebuffer_height == height) {
- break;
- }
-
- if (g_get_monotonic_time() > expiry_time) {
- g_warning(
- "Timed out waiting for OpenGL frame of size %zdx%zd (have %zdx%zd)",
- width, height, framebuffer_width, framebuffer_height);
- break;
- }
-
- g_mutex_unlock(&self->frame_mutex);
- fl_task_runner_wait(self->task_runner, expiry_time);
- g_mutex_lock(&self->frame_mutex);
- }
+ size_t width = gdk_window_get_width(window) * scale_factor;
+ size_t height = gdk_window_get_height(window) * scale_factor;
if (fl_framebuffer_get_shareable(self->framebuffer)) {
g_autoptr(FlFramebuffer) sibling =
@@ -302,49 +281,5 @@
glFlush();
- g_mutex_unlock(&self->frame_mutex);
-
return TRUE;
}
-
-static void fl_compositor_opengl_dispose(GObject* object) {
- FlCompositorOpenGL* self = FL_COMPOSITOR_OPENGL(object);
-
- g_clear_object(&self->shader);
-
- g_clear_object(&self->task_runner);
- g_clear_object(&self->opengl_manager);
- g_clear_object(&self->framebuffer);
- g_clear_pointer(&self->pixels, g_free);
- g_mutex_clear(&self->frame_mutex);
-
- G_OBJECT_CLASS(fl_compositor_opengl_parent_class)->dispose(object);
-}
-
-static void fl_compositor_opengl_class_init(FlCompositorOpenGLClass* klass) {
- FL_COMPOSITOR_CLASS(klass)->present_layers =
- fl_compositor_opengl_present_layers;
- FL_COMPOSITOR_CLASS(klass)->get_frame_size =
- fl_compositor_opengl_get_frame_size;
- FL_COMPOSITOR_CLASS(klass)->render = fl_compositor_opengl_render;
-
- G_OBJECT_CLASS(klass)->dispose = fl_compositor_opengl_dispose;
-}
-
-static void fl_compositor_opengl_init(FlCompositorOpenGL* self) {
- g_mutex_init(&self->frame_mutex);
-}
-
-FlCompositorOpenGL* fl_compositor_opengl_new(FlTaskRunner* task_runner,
- FlOpenGLManager* opengl_manager,
- gboolean shareable) {
- FlCompositorOpenGL* self = FL_COMPOSITOR_OPENGL(
- g_object_new(fl_compositor_opengl_get_type(), nullptr));
-
- self->task_runner = FL_TASK_RUNNER(g_object_ref(task_runner));
- self->shareable = shareable;
- self->opengl_manager = FL_OPENGL_MANAGER(g_object_ref(opengl_manager));
- self->shader = fl_compositor_opengl_shader_new(opengl_manager);
-
- return self;
-}
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 77641a7..448c032 100644
--- a/engine/src/flutter/shell/platform/linux/fl_compositor_opengl.h
+++ b/engine/src/flutter/shell/platform/linux/fl_compositor_opengl.h
@@ -8,10 +8,8 @@
#include <gtk/gtk.h>
#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/fl_opengl_manager.h"
-#include "flutter/shell/platform/linux/fl_task_runner.h"
G_BEGIN_DECLS
@@ -19,29 +17,77 @@
fl_compositor_opengl,
FL,
COMPOSITOR_OPENGL,
- FlCompositor)
+ GObject)
/**
* FlCompositorOpenGL:
*
* #FlCompositorOpenGL is class that implements compositing using OpenGL.
+ *
+ * The composited frame is stored in an OpenGL framebuffer (texture).
+ *
+ * A frame may be written by fl_compositor_opengl_composite_layers using one
+ * OpenGL context and read by fl_compositor_opengl_render using another. When
+ * the compositor is created as shareable the two contexts must belong to the
+ * same share group so the frame texture can be accessed from both, and the
+ * writing context issues a glFlush() so the frame is visible to the reading
+ * context. When not shareable the frame is copied to CPU memory by the writing
+ * context and uploaded into a new texture by the reading context.
*/
/**
* fl_compositor_opengl_new:
- * @task_runner: an #FlTaskRunnner.
* @opengl_manager: an #FlOpenGLManager
- * @shareable: %TRUE if the can use a framebuffer that is shared between
- * contexts.
+ * @shareable: %TRUE if the compositor can use a framebuffer that is shared
+ * between contexts.
*
* Creates a new OpenGL compositor.
*
* Returns: a new #FlCompositorOpenGL.
*/
-FlCompositorOpenGL* fl_compositor_opengl_new(FlTaskRunner* task_runner,
- FlOpenGLManager* opengl_manager,
+FlCompositorOpenGL* fl_compositor_opengl_new(FlOpenGLManager* opengl_manager,
gboolean shareable);
+/**
+ * fl_compositor_opengl_composite_layers:
+ * @compositor: an #FlCompositorOpenGL.
+ * @layers: layers to be composited.
+ * @layers_count: number of layers.
+ *
+ * Composite layers into the stored frame using the current OpenGL context.
+ *
+ * Returns %TRUE if successful.
+ */
+gboolean fl_compositor_opengl_composite_layers(FlCompositorOpenGL* compositor,
+ const FlutterLayer** layers,
+ size_t layers_count);
+
+/**
+ * fl_compositor_opengl_get_frame_size:
+ * @compositor: an #FlCompositorOpenGL.
+ * @width: location to write frame width in pixels.
+ * @height: location to write frame height in pixels.
+ *
+ * Get the size of the layer ready for rendering.
+ */
+void fl_compositor_opengl_get_frame_size(FlCompositorOpenGL* compositor,
+ size_t* width,
+ size_t* height);
+
+/**
+ * fl_compositor_opengl_render:
+ * @compositor: an #FlCompositorOpenGL.
+ * @cr: a Cairo rendering context.
+ * @window: window being rendered into.
+ *
+ * Renders the current frame using the current OpenGL context.
+ *
+ * Returns %TRUE if successful.
+ */
+gboolean fl_compositor_opengl_render(FlCompositorOpenGL* compositor,
+ cairo_t* cr,
+ GdkWindow* window);
+
G_END_DECLS
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_COMPOSITOR_OPENGL_H_
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 acae699..be0b24c 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
@@ -8,7 +8,6 @@
#include "flutter/common/constants.h"
#include "flutter/fml/logging.h"
-#include "flutter/fml/synchronization/waitable_event.h"
#include "flutter/shell/platform/linux/fl_compositor_opengl.h"
#include "flutter/shell/platform/linux/fl_engine_private.h"
#include "flutter/shell/platform/linux/fl_framebuffer.h"
@@ -23,10 +22,9 @@
class FlCompositorOpenGLTest : public flutter::testing::LinuxTest {
protected:
void SetUp() override {
- task_runner = fl_task_runner_new(engine);
opengl_manager = fl_opengl_manager_new();
renderable = fl_mock_renderable_new();
- compositor = fl_compositor_opengl_new(task_runner, opengl_manager, FALSE);
+ compositor = fl_compositor_opengl_new(opengl_manager, FALSE);
fl_engine_set_implicit_view(engine, FL_RENDERABLE(renderable));
}
@@ -34,11 +32,9 @@
g_clear_object(&compositor);
g_clear_object(&renderable);
g_clear_object(&opengl_manager);
- g_clear_object(&task_runner);
}
::testing::NiceMock<flutter::testing::MockEpoxy> epoxy;
- FlTaskRunner* task_runner = nullptr;
FlOpenGLManager* opengl_manager = nullptr;
FlMockRenderable* renderable = nullptr;
FlCompositorOpenGL* compositor = nullptr;
@@ -59,12 +55,11 @@
.size = {width, height}};
const FlutterLayer* layers[1] = {&layer};
std::thread([&]() {
- fl_compositor_present_layers(FL_COMPOSITOR(compositor), layers, 1);
+ fl_compositor_opengl_composite_layers(compositor, layers, 1);
}).join();
size_t frame_width, frame_height;
- fl_compositor_get_frame_size(FL_COMPOSITOR(compositor), &frame_width,
- &frame_height);
+ fl_compositor_opengl_get_frame_size(compositor, &frame_width, &frame_height);
EXPECT_EQ(frame_width, width);
EXPECT_EQ(frame_height, height);
@@ -75,7 +70,7 @@
cairo_surface_t* surface = cairo_image_surface_create_for_data(
image_data, CAIRO_FORMAT_ARGB32, width, height, stride);
cairo_t* cr = cairo_create(surface);
- fl_compositor_render(FL_COMPOSITOR(compositor), cr, nullptr, TRUE);
+ fl_compositor_opengl_render(compositor, cr, nullptr);
cairo_surface_destroy(surface);
cairo_destroy(cr);
}
@@ -94,9 +89,7 @@
.offset = {0, 0},
.size = {width1, height1}};
const FlutterLayer* layers1[1] = {&layer1};
- std::thread([&]() {
- fl_compositor_present_layers(FL_COMPOSITOR(compositor), layers1, 1);
- }).join();
+ fl_compositor_opengl_composite_layers(compositor, layers1, 1);
// Present layer in current size.
constexpr size_t width2 = 100;
@@ -111,24 +104,24 @@
.offset = {0, 0},
.size = {width2, height2}};
const FlutterLayer* layers2[1] = {&layer2};
- fml::AutoResetWaitableEvent latch;
- std::thread([&]() {
- fl_compositor_present_layers(FL_COMPOSITOR(compositor), layers2, 1);
- latch.Signal();
- }).detach();
+ fl_compositor_opengl_composite_layers(compositor, layers2, 1);
- // Render, will wait for the second layer if necessary.
+ // The stored frame is now the new size.
+ size_t frame_width, frame_height;
+ fl_compositor_opengl_get_frame_size(compositor, &frame_width, &frame_height);
+ EXPECT_EQ(frame_width, width2);
+ EXPECT_EQ(frame_height, height2);
+
+ // Render the presented layer.
int stride2 = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width2);
g_autofree unsigned char* image_data =
static_cast<unsigned char*>(malloc(height2 * stride2));
cairo_surface_t* surface = cairo_image_surface_create_for_data(
image_data, CAIRO_FORMAT_ARGB32, width2, height2, stride2);
cairo_t* cr = cairo_create(surface);
- fl_compositor_render(FL_COMPOSITOR(compositor), cr, nullptr, TRUE);
+ fl_compositor_opengl_render(compositor, cr, nullptr);
cairo_surface_destroy(surface);
cairo_destroy(cr);
-
- latch.Wait();
}
TEST_F(FlCompositorOpenGLTest, RestoresGLState) {
@@ -160,7 +153,7 @@
// Present layer and render.
std::thread([&]() {
- fl_compositor_present_layers(FL_COMPOSITOR(compositor), layers, 1);
+ fl_compositor_opengl_composite_layers(compositor, layers, 1);
}).join();
int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
g_autofree unsigned char* image_data =
@@ -168,7 +161,7 @@
cairo_surface_t* surface = cairo_image_surface_create_for_data(
image_data, CAIRO_FORMAT_ARGB32, width, height, stride);
cairo_t* cr = cairo_create(surface);
- fl_compositor_render(FL_COMPOSITOR(compositor), cr, nullptr, TRUE);
+ fl_compositor_opengl_render(compositor, cr, nullptr);
cairo_surface_destroy(surface);
cairo_destroy(cr);
@@ -206,7 +199,7 @@
// Present layer and render.
std::thread([&]() {
- fl_compositor_present_layers(FL_COMPOSITOR(compositor), layers, 1);
+ fl_compositor_opengl_composite_layers(compositor, layers, 1);
}).join();
int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
g_autofree unsigned char* image_data =
@@ -214,7 +207,7 @@
cairo_surface_t* surface = cairo_image_surface_create_for_data(
image_data, CAIRO_FORMAT_ARGB32, width, height, stride);
cairo_t* cr = cairo_create(surface);
- fl_compositor_render(FL_COMPOSITOR(compositor), cr, nullptr, TRUE);
+ fl_compositor_opengl_render(compositor, cr, nullptr);
cairo_surface_destroy(surface);
cairo_destroy(cr);
}
@@ -250,7 +243,7 @@
// Present layer and render.
std::thread([&]() {
- fl_compositor_present_layers(FL_COMPOSITOR(compositor), layers, 1);
+ fl_compositor_opengl_composite_layers(compositor, layers, 1);
}).join();
int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
g_autofree unsigned char* image_data =
@@ -258,7 +251,7 @@
cairo_surface_t* surface = cairo_image_surface_create_for_data(
image_data, CAIRO_FORMAT_ARGB32, width, height, stride);
cairo_t* cr = cairo_create(surface);
- fl_compositor_render(FL_COMPOSITOR(compositor), cr, nullptr, TRUE);
+ fl_compositor_opengl_render(compositor, cr, nullptr);
cairo_surface_destroy(surface);
cairo_destroy(cr);
}
@@ -287,7 +280,7 @@
// Present layer and render.
std::thread([&]() {
- fl_compositor_present_layers(FL_COMPOSITOR(compositor), layers, 1);
+ fl_compositor_opengl_composite_layers(compositor, layers, 1);
}).join();
int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
g_autofree unsigned char* image_data =
@@ -295,7 +288,7 @@
cairo_surface_t* surface = cairo_image_surface_create_for_data(
image_data, CAIRO_FORMAT_ARGB32, width, height, stride);
cairo_t* cr = cairo_create(surface);
- fl_compositor_render(FL_COMPOSITOR(compositor), cr, nullptr, TRUE);
+ fl_compositor_opengl_render(compositor, cr, nullptr);
cairo_surface_destroy(surface);
cairo_destroy(cr);
}
@@ -325,7 +318,7 @@
// Present layer and render.
std::thread([&]() {
- fl_compositor_present_layers(FL_COMPOSITOR(compositor), layers, 1);
+ fl_compositor_opengl_composite_layers(compositor, layers, 1);
}).join();
int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
g_autofree unsigned char* image_data =
@@ -333,7 +326,7 @@
cairo_surface_t* surface = cairo_image_surface_create_for_data(
image_data, CAIRO_FORMAT_ARGB32, width, height, stride);
cairo_t* cr = cairo_create(surface);
- fl_compositor_render(FL_COMPOSITOR(compositor), cr, nullptr, TRUE);
+ fl_compositor_opengl_render(compositor, cr, nullptr);
cairo_surface_destroy(surface);
cairo_destroy(cr);
}
@@ -353,7 +346,7 @@
.size = {width, height}};
const FlutterLayer* layers[1] = {&layer};
std::thread([&]() {
- fl_compositor_present_layers(FL_COMPOSITOR(compositor), layers, 1);
+ fl_compositor_opengl_composite_layers(compositor, layers, 1);
}).join();
// Mock window size to be larger (200x200).
@@ -363,9 +356,8 @@
EXPECT_CALL(mock_gtk, gdk_window_get_height(::testing::_))
.WillRepeatedly(::testing::Return(200));
- // Render with wait_for_frame = FALSE.
- // This should not wait for a new frame, and try to render the 100x100 frame
- // into the 200x200 window.
+ // Render into the larger window.
+ // This renders the 100x100 frame into the 200x200 window without waiting.
// If bug is present, it will try to read 200x200 from 100x100 buffer.
int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, 200);
g_autofree unsigned char* image_data =
@@ -375,7 +367,7 @@
cairo_t* cr = cairo_create(surface);
// We expect this to not crash.
- fl_compositor_render(FL_COMPOSITOR(compositor), cr, nullptr, FALSE);
+ fl_compositor_opengl_render(compositor, cr, nullptr);
cairo_surface_destroy(surface);
cairo_destroy(cr);
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 a6ca967..f604d9b 100644
--- a/engine/src/flutter/shell/platform/linux/fl_compositor_software.cc
+++ b/engine/src/flutter/shell/platform/linux/fl_compositor_software.cc
@@ -5,10 +5,7 @@
#include "fl_compositor_software.h"
struct _FlCompositorSoftware {
- FlCompositor parent_instance;
-
- // Task runner to wait for frames on.
- FlTaskRunner* task_runner;
+ GObject parent_instance;
// Width of frame in pixels.
size_t width;
@@ -18,23 +15,36 @@
// Surface to draw on view.
cairo_surface_t* surface;
-
- // Ensure Flutter and GTK can access the surface.
- GMutex frame_mutex;
};
-G_DEFINE_TYPE(FlCompositorSoftware,
- fl_compositor_software,
- fl_compositor_get_type())
+G_DEFINE_TYPE(FlCompositorSoftware, fl_compositor_software, G_TYPE_OBJECT)
-static gboolean fl_compositor_software_present_layers(
- FlCompositor* compositor,
- const FlutterLayer** layers,
- size_t layers_count) {
- FlCompositorSoftware* self = FL_COMPOSITOR_SOFTWARE(compositor);
+static void fl_compositor_software_dispose(GObject* object) {
+ FlCompositorSoftware* self = FL_COMPOSITOR_SOFTWARE(object);
- g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&self->frame_mutex);
+ if (self->surface != nullptr) {
+ g_free(cairo_image_surface_get_data(self->surface));
+ }
+ g_clear_pointer(&self->surface, cairo_surface_destroy);
+ G_OBJECT_CLASS(fl_compositor_software_parent_class)->dispose(object);
+}
+
+static void fl_compositor_software_class_init(
+ FlCompositorSoftwareClass* klass) {
+ G_OBJECT_CLASS(klass)->dispose = fl_compositor_software_dispose;
+}
+
+static void fl_compositor_software_init(FlCompositorSoftware* self) {}
+
+FlCompositorSoftware* fl_compositor_software_new() {
+ return FL_COMPOSITOR_SOFTWARE(
+ g_object_new(fl_compositor_software_get_type(), nullptr));
+}
+
+gboolean fl_compositor_software_composite_layers(FlCompositorSoftware* self,
+ const FlutterLayer** layers,
+ size_t layers_count) {
if (layers_count == 0) {
return TRUE;
}
@@ -63,18 +73,12 @@
backing_store->software.height, backing_store->software.row_bytes);
}
- fl_task_runner_stop_wait(self->task_runner);
-
return TRUE;
}
-static void fl_compositor_software_get_frame_size(FlCompositor* compositor,
- size_t* width,
- size_t* height) {
- FlCompositorSoftware* self = FL_COMPOSITOR_SOFTWARE(compositor);
-
- g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&self->frame_mutex);
-
+void fl_compositor_software_get_frame_size(FlCompositorSoftware* self,
+ size_t* width,
+ size_t* height) {
if (width != nullptr) {
*width = self->width;
}
@@ -83,82 +87,16 @@
}
}
-static gboolean fl_compositor_software_render(FlCompositor* compositor,
- cairo_t* cr,
- GdkWindow* window,
- gboolean wait_for_frame) {
- FlCompositorSoftware* self = FL_COMPOSITOR_SOFTWARE(compositor);
-
- g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&self->frame_mutex);
-
+gboolean fl_compositor_software_render(FlCompositorSoftware* self,
+ cairo_t* cr,
+ gint scale_factor) {
if (self->surface == nullptr) {
return FALSE;
}
- // If frame not ready, then wait for it.
- gint scale_factor = gdk_window_get_scale_factor(window);
- if (wait_for_frame) {
- gint64 expiry_time =
- g_get_monotonic_time() + kCompositorRenderTimeoutMicroseconds;
- while (true) {
- size_t width = gdk_window_get_width(window) * scale_factor;
- size_t height = gdk_window_get_height(window) * scale_factor;
- if (self->width == width && self->height == height) {
- break;
- }
-
- if (g_get_monotonic_time() > expiry_time) {
- g_warning(
- "Timed out waiting for software frame of size %zdx%zd (have "
- "%zdx%zd)",
- width, height, self->width, self->height);
- break;
- }
-
- g_mutex_unlock(&self->frame_mutex);
- fl_task_runner_wait(self->task_runner, expiry_time);
- g_mutex_lock(&self->frame_mutex);
- }
- }
-
cairo_surface_set_device_scale(self->surface, scale_factor, scale_factor);
cairo_set_source_surface(cr, self->surface, 0.0, 0.0);
cairo_paint(cr);
return TRUE;
}
-
-static void fl_compositor_software_dispose(GObject* object) {
- FlCompositorSoftware* self = FL_COMPOSITOR_SOFTWARE(object);
-
- g_clear_object(&self->task_runner);
- if (self->surface != nullptr) {
- g_free(cairo_image_surface_get_data(self->surface));
- }
- g_clear_pointer(&self->surface, cairo_surface_destroy);
- g_mutex_clear(&self->frame_mutex);
-
- G_OBJECT_CLASS(fl_compositor_software_parent_class)->dispose(object);
-}
-
-static void fl_compositor_software_class_init(
- FlCompositorSoftwareClass* klass) {
- FL_COMPOSITOR_CLASS(klass)->present_layers =
- fl_compositor_software_present_layers;
- FL_COMPOSITOR_CLASS(klass)->get_frame_size =
- fl_compositor_software_get_frame_size;
- FL_COMPOSITOR_CLASS(klass)->render = fl_compositor_software_render;
-
- G_OBJECT_CLASS(klass)->dispose = fl_compositor_software_dispose;
-}
-
-static void fl_compositor_software_init(FlCompositorSoftware* self) {
- g_mutex_init(&self->frame_mutex);
-}
-
-FlCompositorSoftware* fl_compositor_software_new(FlTaskRunner* task_runner) {
- FlCompositorSoftware* self = FL_COMPOSITOR_SOFTWARE(
- g_object_new(fl_compositor_software_get_type(), nullptr));
- self->task_runner = FL_TASK_RUNNER(g_object_ref(task_runner));
- return self;
-}
diff --git a/engine/src/flutter/shell/platform/linux/fl_compositor_software.h b/engine/src/flutter/shell/platform/linux/fl_compositor_software.h
index 27eb93e..a0c8657 100644
--- a/engine/src/flutter/shell/platform/linux/fl_compositor_software.h
+++ b/engine/src/flutter/shell/platform/linux/fl_compositor_software.h
@@ -6,9 +6,9 @@
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_COMPOSITOR_SOFTWARE_H_
#include <cairo/cairo.h>
+#include <glib-object.h>
-#include "flutter/shell/platform/linux/fl_compositor.h"
-#include "flutter/shell/platform/linux/fl_task_runner.h"
+#include "flutter/shell/platform/embedder/embedder.h"
G_BEGIN_DECLS
@@ -16,24 +16,68 @@
fl_compositor_software,
FL,
COMPOSITOR_SOFTWARE,
- FlCompositor)
+ GObject)
/**
* FlCompositorSoftware:
*
* #FlCompositorSoftware is a class that implements compositing using software
* rendering.
+ *
+ * The composited frame is stored in a Cairo image surface.
*/
/**
* fl_compositor_software_new:
- * @task_runner: an #FlTaskRunnner.
*
* Creates a new software rendering compositor.
*
* Returns: a new #FlCompositorSoftware.
*/
-FlCompositorSoftware* fl_compositor_software_new(FlTaskRunner* task_runner);
+FlCompositorSoftware* fl_compositor_software_new();
+
+/**
+ * fl_compositor_software_composite_layers:
+ * @compositor: an #FlCompositorSoftware.
+ * @layers: layers to be composited. Each layer must be a backing store layer
+ * (%kFlutterLayerContentTypeBackingStore) backed by a software backing store
+ * (%kFlutterBackingStoreTypeSoftware).
+ * @layers_count: number of layers.
+ *
+ * Combines and stores the provided layers as the current frame.
+ *
+ * Returns %TRUE if successful.
+ */
+gboolean fl_compositor_software_composite_layers(
+ FlCompositorSoftware* compositor,
+ const FlutterLayer** layers,
+ size_t layers_count);
+
+/**
+ * fl_compositor_software_get_frame_size:
+ * @compositor: an #FlCompositorSoftware.
+ * @width: location to write frame width in pixels.
+ * @height: location to write frame height in pixels.
+ *
+ * Get the size of the stored frame. The size is zero if there is no frame yet.
+ */
+void fl_compositor_software_get_frame_size(FlCompositorSoftware* compositor,
+ size_t* width,
+ size_t* height);
+
+/**
+ * fl_compositor_software_render:
+ * @compositor: an #FlCompositorSoftware.
+ * @cr: a Cairo rendering context.
+ * @scale_factor: the device scale factor to render at.
+ *
+ * Renders the stored frame.
+ *
+ * Returns %TRUE if successful.
+ */
+gboolean fl_compositor_software_render(FlCompositorSoftware* compositor,
+ cairo_t* cr,
+ gint scale_factor);
G_END_DECLS
diff --git a/engine/src/flutter/shell/platform/linux/fl_compositor_software_test.cc b/engine/src/flutter/shell/platform/linux/fl_compositor_software_test.cc
index e28bd85..b478b9b 100644
--- a/engine/src/flutter/shell/platform/linux/fl_compositor_software_test.cc
+++ b/engine/src/flutter/shell/platform/linux/fl_compositor_software_test.cc
@@ -6,27 +6,14 @@
#include "flutter/shell/platform/linux/testing/linux_test.h"
#include "gtest/gtest.h"
-#include "flutter/common/constants.h"
-#include "flutter/fml/synchronization/waitable_event.h"
#include "flutter/shell/platform/linux/fl_compositor_software.h"
-#include "flutter/shell/platform/linux/fl_task_runner.h"
-#include "flutter/shell/platform/linux/public/flutter_linux/fl_dart_project.h"
-
-#include <gdk/gdkwayland.h>
class FlCompositorSoftwareTest : public flutter::testing::LinuxTest {
protected:
- void SetUp() override {
- task_runner = fl_task_runner_new(engine);
- compositor = fl_compositor_software_new(task_runner);
- }
+ void SetUp() override { compositor = fl_compositor_software_new(); }
- ~FlCompositorSoftwareTest() {
- g_clear_object(&compositor);
- g_clear_object(&task_runner);
- }
+ ~FlCompositorSoftwareTest() { g_clear_object(&compositor); }
- FlTaskRunner* task_runner = nullptr;
FlCompositorSoftware* compositor = nullptr;
};
@@ -46,13 +33,11 @@
.offset = {0, 0},
.size = {width, height}};
const FlutterLayer* layers[1] = {&layer};
- std::thread([&]() {
- fl_compositor_present_layers(FL_COMPOSITOR(compositor), layers, 1);
- }).join();
+ fl_compositor_software_composite_layers(compositor, layers, 1);
size_t frame_width, frame_height;
- fl_compositor_get_frame_size(FL_COMPOSITOR(compositor), &frame_width,
- &frame_height);
+ fl_compositor_software_get_frame_size(compositor, &frame_width,
+ &frame_height);
EXPECT_EQ(frame_width, width);
EXPECT_EQ(frame_height, height);
@@ -63,7 +48,7 @@
cairo_surface_t* surface = cairo_image_surface_create_for_data(
image_data, CAIRO_FORMAT_ARGB32, width, height, stride);
cairo_t* cr = cairo_create(surface);
- fl_compositor_render(FL_COMPOSITOR(compositor), cr, nullptr, TRUE);
+ EXPECT_TRUE(fl_compositor_software_render(compositor, cr, 1));
cairo_surface_destroy(surface);
cairo_destroy(cr);
}
@@ -85,11 +70,9 @@
.offset = {0, 0},
.size = {width1, height1}};
const FlutterLayer* layers1[1] = {&layer1};
- std::thread([&]() {
- fl_compositor_present_layers(FL_COMPOSITOR(compositor), layers1, 1);
- }).join();
+ fl_compositor_software_composite_layers(compositor, layers1, 1);
- // Present layer in current size.
+ // Present a layer in the new size.
constexpr size_t width2 = 100;
constexpr size_t height2 = 100;
row_bytes = width2 * 4;
@@ -105,22 +88,23 @@
.offset = {0, 0},
.size = {width2, height2}};
const FlutterLayer* layers2[1] = {&layer2};
- fml::AutoResetWaitableEvent latch;
- std::thread([&]() {
- fl_compositor_present_layers(FL_COMPOSITOR(compositor), layers2, 1);
- latch.Signal();
- }).detach();
+ fl_compositor_software_composite_layers(compositor, layers2, 1);
- // Render, will wait for the second layer if necessary.
+ // The stored frame is now the new size.
+ size_t frame_width, frame_height;
+ fl_compositor_software_get_frame_size(compositor, &frame_width,
+ &frame_height);
+ EXPECT_EQ(frame_width, width2);
+ EXPECT_EQ(frame_height, height2);
+
+ // Render the presented layer.
int stride2 = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width2);
g_autofree unsigned char* image_data =
static_cast<unsigned char*>(malloc(height2 * stride2));
cairo_surface_t* surface = cairo_image_surface_create_for_data(
image_data, CAIRO_FORMAT_ARGB32, width2, height2, stride2);
cairo_t* cr = cairo_create(surface);
- fl_compositor_render(FL_COMPOSITOR(compositor), cr, nullptr, TRUE);
+ EXPECT_TRUE(fl_compositor_software_render(compositor, cr, 1));
cairo_surface_destroy(surface);
cairo_destroy(cr);
-
- latch.Wait();
}
diff --git a/engine/src/flutter/shell/platform/linux/fl_view.cc b/engine/src/flutter/shell/platform/linux/fl_view.cc
index 4340746..f209219 100644
--- a/engine/src/flutter/shell/platform/linux/fl_view.cc
+++ b/engine/src/flutter/shell/platform/linux/fl_view.cc
@@ -23,6 +23,8 @@
#include "flutter/shell/platform/linux/fl_view_accessible.h"
#include "flutter/shell/platform/linux/fl_view_private.h"
#include "flutter/shell/platform/linux/fl_view_renderer.h"
+#include "flutter/shell/platform/linux/fl_view_renderer_opengl.h"
+#include "flutter/shell/platform/linux/fl_view_renderer_software.h"
#include "flutter/shell/platform/linux/fl_window_state_monitor.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_engine.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_plugin_registry.h"
@@ -644,7 +646,17 @@
G_CALLBACK(update_semantics_cb), self,
G_CONNECT_SWAPPED);
- self->renderer = fl_view_renderer_new(self->engine, self->sized_to_content);
+ switch (fl_engine_get_renderer_type(self->engine)) {
+ case kSoftware:
+ self->renderer = FL_VIEW_RENDERER(
+ fl_view_renderer_software_new(self->engine, self->sized_to_content));
+ break;
+ case kOpenGL:
+ default:
+ self->renderer = FL_VIEW_RENDERER(
+ fl_view_renderer_opengl_new(self->engine, self->sized_to_content));
+ break;
+ }
gtk_widget_show(GTK_WIDGET(self->renderer));
gtk_container_add(GTK_CONTAINER(self->event_box), GTK_WIDGET(self->renderer));
g_signal_connect_swapped(self->renderer, "realize", G_CALLBACK(realize_cb),
diff --git a/engine/src/flutter/shell/platform/linux/fl_view_renderer.cc b/engine/src/flutter/shell/platform/linux/fl_view_renderer.cc
index dba92d9..bc4143f 100644
--- a/engine/src/flutter/shell/platform/linux/fl_view_renderer.cc
+++ b/engine/src/flutter/shell/platform/linux/fl_view_renderer.cc
@@ -4,200 +4,43 @@
#include "flutter/shell/platform/linux/fl_view_renderer.h"
-#include <gdk/gdkwayland.h>
-
-#include "flutter/shell/platform/linux/fl_compositor.h"
-#include "flutter/shell/platform/linux/fl_compositor_opengl.h"
-#include "flutter/shell/platform/linux/fl_compositor_software.h"
-#include "flutter/shell/platform/linux/fl_engine_private.h"
-#include "flutter/shell/platform/linux/fl_opengl_manager.h"
-
-struct _FlViewRenderer {
- GtkDrawingArea parent_instance;
-
- // Engine this widget is rendering.
- FlEngine* engine;
-
- // Rendering context when using OpenGL.
- GdkGLContext* render_context;
-
- // Combines layers into frame.
- FlCompositor* compositor;
-
- // Background color.
+typedef struct {
+ // Background color drawn behind the Flutter frame.
GdkRGBA* background_color;
- // TRUE if the view size should be controlled by Flutter.
- gboolean sized_to_content;
-
// TRUE if have got the first frame to render.
gboolean have_first_frame;
-};
+} FlViewRendererPrivate;
enum { SIGNAL_FIRST_FRAME, LAST_SIGNAL };
static guint fl_view_renderer_signals[LAST_SIGNAL];
-G_DEFINE_TYPE(FlViewRenderer, fl_view_renderer, GTK_TYPE_DRAWING_AREA)
+G_DEFINE_TYPE_WITH_PRIVATE(FlViewRenderer,
+ fl_view_renderer,
+ GTK_TYPE_DRAWING_AREA)
-// Redraw the view from the GTK thread.
-static gboolean redraw_cb(gpointer user_data) {
- g_autoptr(FlViewRenderer) self = FL_VIEW_RENDERER(user_data);
-
- if (self->compositor == nullptr) {
- return G_SOURCE_REMOVE;
- }
-
- if (!self->have_first_frame) {
- self->have_first_frame = TRUE;
- g_signal_emit(self, fl_view_renderer_signals[SIGNAL_FIRST_FRAME], 0);
- }
-
- // If Flutter is controlling the window size, then resize the view if
- // necessary.
- GtkWidget* render_widget = GTK_WIDGET(self);
- GtkAllocation allocation;
- gtk_widget_get_allocation(render_widget, &allocation);
- gint scale_factor = gtk_widget_get_scale_factor(render_widget);
- size_t width = allocation.width * scale_factor;
- size_t height = allocation.height * scale_factor;
- size_t frame_width, frame_height;
- fl_compositor_get_frame_size(self->compositor, &frame_width, &frame_height);
- gboolean frame_size_matches = width == frame_width && height == frame_height;
- if (self->sized_to_content && !frame_size_matches) {
- gtk_widget_set_size_request(render_widget, frame_width / scale_factor,
- frame_height / scale_factor);
- GtkWidget* toplevel = gtk_widget_get_toplevel(render_widget);
- if (GTK_IS_WINDOW(toplevel)) {
- // Resize to smallest size, so that the window will shrink to fit the new
- // size of the render area.
- gtk_window_resize(GTK_WINDOW(toplevel), 1, 1);
- }
- return G_SOURCE_REMOVE;
- }
-
- gtk_widget_queue_draw(render_widget);
-
- return G_SOURCE_REMOVE;
-}
-
-static void setup_opengl(FlViewRenderer* self) {
- g_autoptr(GError) error = nullptr;
-
- self->render_context = gdk_window_create_gl_context(
- gtk_widget_get_window(GTK_WIDGET(self)), &error);
- if (self->render_context == nullptr) {
- g_warning("Failed to create OpenGL context: %s", error->message);
- return;
- }
-
- if (!gdk_gl_context_realize(self->render_context, &error)) {
- g_warning("Failed to realize OpenGL context: %s", error->message);
- return;
- }
-
- // 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.
- gboolean shareable =
- GDK_IS_WAYLAND_DISPLAY(gtk_widget_get_display(GTK_WIDGET(self)));
- self->compositor = FL_COMPOSITOR(fl_compositor_opengl_new(
- fl_engine_get_task_runner(self->engine),
- fl_engine_get_opengl_manager(self->engine), shareable));
-}
-
-static void setup_software(FlViewRenderer* self) {
- self->compositor = FL_COMPOSITOR(
- fl_compositor_software_new(fl_engine_get_task_runner(self->engine)));
-}
-
-static void paint_background(FlViewRenderer* self, cairo_t* cr) {
- // Don't bother drawing if fully transparent - the widget above this will
- // already be drawn by GTK.
- if (self->background_color->red == 0 && self->background_color->green == 0 &&
- self->background_color->blue == 0 && self->background_color->alpha == 0) {
- return;
- }
-
- gdk_cairo_set_source_rgba(cr, self->background_color);
- cairo_paint(cr);
-}
-
-// Implements GtkWidget::realize.
-static void fl_view_renderer_realize(GtkWidget* widget) {
- FlViewRenderer* self = FL_VIEW_RENDERER(widget);
-
- GTK_WIDGET_CLASS(fl_view_renderer_parent_class)->realize(widget);
-
- switch (fl_engine_get_renderer_type(self->engine)) {
- case kOpenGL:
- setup_opengl(self);
- break;
- case kSoftware:
- setup_software(self);
- break;
- default:
- break;
- }
-}
-
-// Implements GtkWidget::draw.
-static gboolean fl_view_renderer_draw(GtkWidget* widget, cairo_t* cr) {
- FlViewRenderer* self = FL_VIEW_RENDERER(widget);
-
- paint_background(self, cr);
-
- // The compositor is created when the widget is realized; if it is not yet
- // available there is nothing to render beyond the background.
- if (self->compositor == nullptr) {
- return TRUE;
- }
-
- if (self->render_context) {
- gdk_gl_context_make_current(self->render_context);
- }
-
- gboolean wait_for_frame = !self->sized_to_content;
- gboolean result = fl_compositor_render(
- self->compositor, cr, gtk_widget_get_window(widget), wait_for_frame);
-
- if (self->render_context) {
- gdk_gl_context_clear_current();
- }
-
- return result;
+// Default implementation for the abstract present_layers method. Subclasses
+// must override this.
+static void fl_view_renderer_present_layers_default(FlViewRenderer* self,
+ const FlutterLayer** layers,
+ size_t layers_count) {
+ g_assert_not_reached();
}
static void fl_view_renderer_dispose(GObject* object) {
- FlViewRenderer* self = FL_VIEW_RENDERER(object);
+ FlViewRendererPrivate* priv = static_cast<FlViewRendererPrivate*>(
+ fl_view_renderer_get_instance_private(FL_VIEW_RENDERER(object)));
- g_clear_object(&self->render_context);
- g_clear_object(&self->engine);
- g_clear_pointer(&self->background_color, gdk_rgba_free);
+ g_clear_pointer(&priv->background_color, gdk_rgba_free);
G_OBJECT_CLASS(fl_view_renderer_parent_class)->dispose(object);
}
-static void fl_view_renderer_finalize(GObject* object) {
- FlViewRenderer* self = FL_VIEW_RENDERER(object);
-
- // The compositor is released here rather than in dispose() so it outlives a
- // forced dispose (e.g. gtk_widget_destroy()) and is only freed once the last
- // reference is dropped. This keeps it alive for the raster thread, which
- // holds a strong reference on the view (and thus this renderer) while
- // presenting.
- g_clear_object(&self->compositor);
-
- G_OBJECT_CLASS(fl_view_renderer_parent_class)->finalize(object);
-}
-
static void fl_view_renderer_class_init(FlViewRendererClass* klass) {
G_OBJECT_CLASS(klass)->dispose = fl_view_renderer_dispose;
- G_OBJECT_CLASS(klass)->finalize = fl_view_renderer_finalize;
- GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
- widget_class->realize = fl_view_renderer_realize;
- widget_class->draw = fl_view_renderer_draw;
+ klass->present_layers = fl_view_renderer_present_layers_default;
fl_view_renderer_signals[SIGNAL_FIRST_FRAME] =
g_signal_new("first-frame", fl_view_renderer_get_type(),
@@ -205,29 +48,43 @@
}
static void fl_view_renderer_init(FlViewRenderer* self) {
+ FlViewRendererPrivate* priv = static_cast<FlViewRendererPrivate*>(
+ fl_view_renderer_get_instance_private(self));
+
GdkRGBA default_background = {
.red = 0.0, .green = 0.0, .blue = 0.0, .alpha = 1.0};
- self->background_color = gdk_rgba_copy(&default_background);
-}
-
-FlViewRenderer* fl_view_renderer_new(FlEngine* engine,
- gboolean sized_to_content) {
- g_return_val_if_fail(FL_IS_ENGINE(engine), nullptr);
-
- FlViewRenderer* self =
- FL_VIEW_RENDERER(g_object_new(fl_view_renderer_get_type(), nullptr));
-
- self->engine = FL_ENGINE(g_object_ref(engine));
- self->sized_to_content = sized_to_content;
-
- return self;
+ priv->background_color = gdk_rgba_copy(&default_background);
}
void fl_view_renderer_set_background_color(FlViewRenderer* self,
const GdkRGBA* color) {
g_return_if_fail(FL_IS_VIEW_RENDERER(self));
- gdk_rgba_free(self->background_color);
- self->background_color = gdk_rgba_copy(color);
+
+ FlViewRendererPrivate* priv = static_cast<FlViewRendererPrivate*>(
+ fl_view_renderer_get_instance_private(self));
+
+ gdk_rgba_free(priv->background_color);
+ priv->background_color = gdk_rgba_copy(color);
+
+ // Redraw so the new background color is shown.
+ gtk_widget_queue_draw(GTK_WIDGET(self));
+}
+
+void fl_view_renderer_paint_background(FlViewRenderer* self, cairo_t* cr) {
+ g_return_if_fail(FL_IS_VIEW_RENDERER(self));
+
+ FlViewRendererPrivate* priv = static_cast<FlViewRendererPrivate*>(
+ fl_view_renderer_get_instance_private(self));
+
+ // Don't bother drawing if fully transparent - the widget above this will
+ // already be drawn by GTK.
+ if (priv->background_color->red == 0 && priv->background_color->green == 0 &&
+ priv->background_color->blue == 0 && priv->background_color->alpha == 0) {
+ return;
+ }
+
+ gdk_cairo_set_source_rgba(cr, priv->background_color);
+ cairo_paint(cr);
}
void fl_view_renderer_present_layers(FlViewRenderer* self,
@@ -235,14 +92,18 @@
size_t layers_count) {
g_return_if_fail(FL_IS_VIEW_RENDERER(self));
- // Frames may be presented before the widget is realized and the compositor
- // is set up; ignore them.
- if (self->compositor == nullptr) {
- return;
+ FlViewRendererClass* klass = FL_VIEW_RENDERER_GET_CLASS(self);
+ klass->present_layers(self, layers, layers_count);
+}
+
+void fl_view_renderer_notify_frame(FlViewRenderer* self) {
+ g_return_if_fail(FL_IS_VIEW_RENDERER(self));
+
+ FlViewRendererPrivate* priv = static_cast<FlViewRendererPrivate*>(
+ fl_view_renderer_get_instance_private(self));
+
+ if (!priv->have_first_frame) {
+ priv->have_first_frame = TRUE;
+ g_signal_emit(self, fl_view_renderer_signals[SIGNAL_FIRST_FRAME], 0);
}
-
- fl_compositor_present_layers(self->compositor, layers, layers_count);
-
- // Perform the redraw in the GTK thread.
- g_idle_add(redraw_cb, g_object_ref(self));
}
diff --git a/engine/src/flutter/shell/platform/linux/fl_view_renderer.h b/engine/src/flutter/shell/platform/linux/fl_view_renderer.h
index 238a9d8..b00f778 100644
--- a/engine/src/flutter/shell/platform/linux/fl_view_renderer.h
+++ b/engine/src/flutter/shell/platform/linux/fl_view_renderer.h
@@ -8,36 +8,38 @@
#include <gtk/gtk.h>
#include "flutter/shell/platform/embedder/embedder.h"
-#include "flutter/shell/platform/linux/public/flutter_linux/fl_engine.h"
G_BEGIN_DECLS
-G_DECLARE_FINAL_TYPE(FlViewRenderer,
- fl_view_renderer,
- FL,
- VIEW_RENDERER,
- GtkDrawingArea)
+G_DECLARE_DERIVABLE_TYPE(FlViewRenderer,
+ fl_view_renderer,
+ FL,
+ VIEW_RENDERER,
+ GtkDrawingArea)
/**
* FlViewRenderer:
*
* #FlViewRenderer is a GTK widget that renders the contents of a Flutter view.
- * It owns the compositor and OpenGL context used to draw frames produced by the
- * Flutter engine. Input handling and other view responsibilities are handled by
- * #FlView.
+ * Input handling and other view responsibilities are handled by #FlView.
+ *
+ * Subclasses implement rendering for a particular backend, for example
+ * #FlViewRendererOpenGL and #FlViewRendererSoftware. The "first-frame" signal
+ * is emitted when the first frame has been rendered.
*/
-/**
- * fl_view_renderer_new:
- * @engine: the #FlEngine to render.
- * @sized_to_content: %TRUE if the view size is controlled by Flutter.
- *
- * Creates a new widget that renders Flutter frames.
- *
- * Returns: a new #FlViewRenderer.
- */
-FlViewRenderer* fl_view_renderer_new(FlEngine* engine,
- gboolean sized_to_content);
+struct _FlViewRendererClass {
+ GtkDrawingAreaClass parent_class;
+
+ /**
+ * Composites a frame into the renderer. May be called from any thread.
+ *
+ * This method is abstract and must be implemented by subclasses.
+ */
+ void (*present_layers)(FlViewRenderer* renderer,
+ const FlutterLayer** layers,
+ size_t layers_count);
+};
/**
* fl_view_renderer_set_background_color:
@@ -50,6 +52,16 @@
const GdkRGBA* color);
/**
+ * fl_view_renderer_paint_background:
+ * @renderer: an #FlViewRenderer.
+ * @cr: a #cairo_t to paint to.
+ *
+ * Paints the background color behind the Flutter frame. Subclasses call this
+ * at the start of their draw implementation.
+ */
+void fl_view_renderer_paint_background(FlViewRenderer* renderer, cairo_t* cr);
+
+/**
* fl_view_renderer_present_layers:
* @renderer: an #FlViewRenderer.
* @layers: layers to draw.
@@ -62,6 +74,15 @@
const FlutterLayer** layers,
size_t layers_count);
+/**
+ * fl_view_renderer_notify_frame:
+ * @renderer: an #FlViewRenderer.
+ *
+ * Notifies that a frame has been rendered. Subclasses call this on each frame.
+ * The "first-frame" signal is emitted on the first call.
+ */
+void fl_view_renderer_notify_frame(FlViewRenderer* renderer);
+
G_END_DECLS
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_VIEW_RENDERER_H_
diff --git a/engine/src/flutter/shell/platform/linux/fl_view_renderer_opengl.cc b/engine/src/flutter/shell/platform/linux/fl_view_renderer_opengl.cc
new file mode 100644
index 0000000..7a3f955
--- /dev/null
+++ b/engine/src/flutter/shell/platform/linux/fl_view_renderer_opengl.cc
@@ -0,0 +1,258 @@
+// 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.
+
+#include "flutter/shell/platform/linux/fl_view_renderer_opengl.h"
+
+#include <gdk/gdkwayland.h>
+
+#include "flutter/shell/platform/linux/fl_compositor_opengl.h"
+#include "flutter/shell/platform/linux/fl_engine_private.h"
+#include "flutter/shell/platform/linux/fl_opengl_manager.h"
+#include "flutter/shell/platform/linux/fl_task_runner.h"
+
+// Maximum time to wait for a frame to be ready before giving up and rendering.
+static constexpr gint64 kRenderTimeoutMicroseconds = 100000; // 100ms
+
+struct _FlViewRendererOpenGL {
+ FlViewRenderer parent_instance;
+
+ // Engine this widget is rendering.
+ FlEngine* engine;
+
+ // TRUE if the view size should be controlled by Flutter.
+ gboolean sized_to_content;
+
+ // Rendering context for OpenGL.
+ GdkGLContext* render_context;
+
+ // Combines layers into frame.
+ FlCompositorOpenGL* compositor;
+
+ // Task runner to wait for frames on.
+ FlTaskRunner* task_runner;
+
+ // Ensure Flutter and GTK can access the frame stored in the compositor.
+ GMutex frame_mutex;
+};
+
+G_DEFINE_TYPE(FlViewRendererOpenGL,
+ fl_view_renderer_opengl,
+ fl_view_renderer_get_type())
+
+// Redraw the view from the GTK thread.
+static gboolean redraw_cb(gpointer user_data) {
+ g_autoptr(FlViewRendererOpenGL) self = FL_VIEW_RENDERER_OPENGL(user_data);
+
+ if (self->compositor == nullptr) {
+ return G_SOURCE_REMOVE;
+ }
+
+ fl_view_renderer_notify_frame(FL_VIEW_RENDERER(self));
+
+ // If Flutter is controlling the window size, then resize the view if
+ // necessary.
+ GtkWidget* render_widget = GTK_WIDGET(self);
+ GtkAllocation allocation;
+ gtk_widget_get_allocation(render_widget, &allocation);
+ gint scale_factor = gtk_widget_get_scale_factor(render_widget);
+ size_t width = allocation.width * scale_factor;
+ size_t height = allocation.height * scale_factor;
+ size_t frame_width, frame_height;
+ {
+ g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&self->frame_mutex);
+ fl_compositor_opengl_get_frame_size(self->compositor, &frame_width,
+ &frame_height);
+ }
+ gboolean frame_size_matches = width == frame_width && height == frame_height;
+ if (self->sized_to_content && !frame_size_matches) {
+ gtk_widget_set_size_request(render_widget, frame_width / scale_factor,
+ frame_height / scale_factor);
+ GtkWidget* toplevel = gtk_widget_get_toplevel(render_widget);
+ if (GTK_IS_WINDOW(toplevel)) {
+ // Resize to smallest size, so that the window will shrink to fit the new
+ // size of the render area.
+ gtk_window_resize(GTK_WINDOW(toplevel), 1, 1);
+ }
+ return G_SOURCE_REMOVE;
+ }
+
+ gtk_widget_queue_draw(render_widget);
+
+ return G_SOURCE_REMOVE;
+}
+
+// Wait for a frame matching the window size to be ready, or until the timeout
+// expires. Must be called with the frame mutex held; the mutex is still held
+// when this function returns.
+static void wait_for_frame(FlViewRendererOpenGL* self,
+ GdkWindow* window,
+ gint scale_factor) {
+ gint64 expiry_time = g_get_monotonic_time() + kRenderTimeoutMicroseconds;
+ while (true) {
+ size_t width = gdk_window_get_width(window) * scale_factor;
+ size_t height = gdk_window_get_height(window) * scale_factor;
+ size_t frame_width, frame_height;
+ fl_compositor_opengl_get_frame_size(self->compositor, &frame_width,
+ &frame_height);
+ if (frame_width == width && frame_height == height) {
+ break;
+ }
+
+ if (g_get_monotonic_time() > expiry_time) {
+ g_warning(
+ "Timed out waiting for OpenGL frame of size %zdx%zd (have "
+ "%zdx%zd)",
+ width, height, frame_width, frame_height);
+ break;
+ }
+
+ g_mutex_unlock(&self->frame_mutex);
+ fl_task_runner_wait(self->task_runner, expiry_time);
+ g_mutex_lock(&self->frame_mutex);
+ }
+}
+
+// Implements GtkWidget::realize.
+static void fl_view_renderer_opengl_realize(GtkWidget* widget) {
+ FlViewRendererOpenGL* self = FL_VIEW_RENDERER_OPENGL(widget);
+
+ GTK_WIDGET_CLASS(fl_view_renderer_opengl_parent_class)->realize(widget);
+
+ g_autoptr(GError) error = nullptr;
+ self->render_context = gdk_window_create_gl_context(
+ gtk_widget_get_window(GTK_WIDGET(self)), &error);
+ if (self->render_context == nullptr) {
+ g_warning("Failed to create OpenGL context: %s", error->message);
+ return;
+ }
+
+ if (!gdk_gl_context_realize(self->render_context, &error)) {
+ g_warning("Failed to realize OpenGL context: %s", error->message);
+ g_clear_object(&self->render_context);
+ return;
+ }
+
+ // 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.
+ gboolean shareable =
+ GDK_IS_WAYLAND_DISPLAY(gtk_widget_get_display(GTK_WIDGET(self)));
+ self->task_runner =
+ FL_TASK_RUNNER(g_object_ref(fl_engine_get_task_runner(self->engine)));
+ self->compositor = fl_compositor_opengl_new(
+ fl_engine_get_opengl_manager(self->engine), shareable);
+}
+
+// Implements GtkWidget::draw.
+static gboolean fl_view_renderer_opengl_draw(GtkWidget* widget, cairo_t* cr) {
+ FlViewRendererOpenGL* self = FL_VIEW_RENDERER_OPENGL(widget);
+
+ fl_view_renderer_paint_background(FL_VIEW_RENDERER(self), cr);
+
+ // The compositor is created when the widget is realized; if it is not yet
+ // available there is nothing to render beyond the background.
+ if (self->compositor == nullptr) {
+ return TRUE;
+ }
+
+ GdkWindow* window = gtk_widget_get_window(widget);
+ gint scale_factor = gdk_window_get_scale_factor(window);
+
+ g_mutex_lock(&self->frame_mutex);
+
+ // If frame not ready, then wait for it.
+ if (!self->sized_to_content) {
+ wait_for_frame(self, window, scale_factor);
+ }
+
+ if (self->render_context != nullptr) {
+ gdk_gl_context_make_current(self->render_context);
+ }
+
+ gboolean result = fl_compositor_opengl_render(self->compositor, cr, window);
+
+ if (self->render_context != nullptr) {
+ gdk_gl_context_clear_current();
+ }
+
+ g_mutex_unlock(&self->frame_mutex);
+
+ return result;
+}
+
+// Implements FlViewRenderer::present_layers.
+static void fl_view_renderer_opengl_present_layers(FlViewRenderer* renderer,
+ const FlutterLayer** layers,
+ size_t layers_count) {
+ FlViewRendererOpenGL* self = FL_VIEW_RENDERER_OPENGL(renderer);
+
+ // Frames may be presented before the widget is realized and the compositor
+ // is set up; ignore them.
+ if (self->compositor == nullptr) {
+ return;
+ }
+
+ g_mutex_lock(&self->frame_mutex);
+ fl_compositor_opengl_composite_layers(self->compositor, layers, layers_count);
+ g_mutex_unlock(&self->frame_mutex);
+
+ // Wake up the GTK thread if it is waiting for this frame.
+ fl_task_runner_stop_wait(self->task_runner);
+
+ // Perform the redraw in the GTK thread.
+ g_idle_add(redraw_cb, g_object_ref(self));
+}
+
+static void fl_view_renderer_opengl_dispose(GObject* object) {
+ FlViewRendererOpenGL* self = FL_VIEW_RENDERER_OPENGL(object);
+
+ g_clear_object(&self->engine);
+ g_clear_object(&self->render_context);
+ g_clear_object(&self->task_runner);
+ g_mutex_clear(&self->frame_mutex);
+
+ G_OBJECT_CLASS(fl_view_renderer_opengl_parent_class)->dispose(object);
+}
+
+static void fl_view_renderer_opengl_finalize(GObject* object) {
+ FlViewRendererOpenGL* self = FL_VIEW_RENDERER_OPENGL(object);
+
+ // The compositor is released here rather than in dispose() so it outlives a
+ // forced dispose (e.g. gtk_widget_destroy()) and is only freed once the last
+ // reference is dropped. This keeps it alive for the raster thread, which
+ // holds a strong reference on the view (and thus this renderer) while
+ // presenting.
+ g_clear_object(&self->compositor);
+
+ G_OBJECT_CLASS(fl_view_renderer_opengl_parent_class)->finalize(object);
+}
+
+static void fl_view_renderer_opengl_class_init(
+ FlViewRendererOpenGLClass* klass) {
+ GObjectClass* object_class = G_OBJECT_CLASS(klass);
+ object_class->dispose = fl_view_renderer_opengl_dispose;
+ object_class->finalize = fl_view_renderer_opengl_finalize;
+
+ GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
+ widget_class->realize = fl_view_renderer_opengl_realize;
+ widget_class->draw = fl_view_renderer_opengl_draw;
+
+ FlViewRendererClass* renderer_class = FL_VIEW_RENDERER_CLASS(klass);
+ renderer_class->present_layers = fl_view_renderer_opengl_present_layers;
+}
+
+static void fl_view_renderer_opengl_init(FlViewRendererOpenGL* self) {
+ g_mutex_init(&self->frame_mutex);
+}
+
+FlViewRendererOpenGL* fl_view_renderer_opengl_new(FlEngine* engine,
+ gboolean sized_to_content) {
+ g_return_val_if_fail(FL_IS_ENGINE(engine), nullptr);
+
+ FlViewRendererOpenGL* self = FL_VIEW_RENDERER_OPENGL(
+ g_object_new(fl_view_renderer_opengl_get_type(), nullptr));
+ self->engine = FL_ENGINE(g_object_ref(engine));
+ self->sized_to_content = sized_to_content;
+ return self;
+}
diff --git a/engine/src/flutter/shell/platform/linux/fl_view_renderer_opengl.h b/engine/src/flutter/shell/platform/linux/fl_view_renderer_opengl.h
new file mode 100644
index 0000000..c0d07d5
--- /dev/null
+++ b/engine/src/flutter/shell/platform/linux/fl_view_renderer_opengl.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_FL_VIEW_RENDERER_OPENGL_H_
+#define FLUTTER_SHELL_PLATFORM_LINUX_FL_VIEW_RENDERER_OPENGL_H_
+
+#include "flutter/shell/platform/linux/fl_view_renderer.h"
+#include "flutter/shell/platform/linux/public/flutter_linux/fl_engine.h"
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE(FlViewRendererOpenGL,
+ fl_view_renderer_opengl,
+ FL,
+ VIEW_RENDERER_OPENGL,
+ FlViewRenderer)
+
+/**
+ * FlViewRendererOpenGL:
+ *
+ * #FlViewRendererOpenGL is an #FlViewRenderer that renders Flutter frames using
+ * OpenGL.
+ */
+
+/**
+ * fl_view_renderer_opengl_new:
+ * @engine: the #FlEngine to render.
+ * @sized_to_content: %TRUE if the view size is controlled by Flutter.
+ *
+ * Creates a new widget that renders Flutter frames using OpenGL.
+ *
+ * Returns: a new #FlViewRendererOpenGL.
+ */
+FlViewRendererOpenGL* fl_view_renderer_opengl_new(FlEngine* engine,
+ gboolean sized_to_content);
+
+G_END_DECLS
+
+#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_VIEW_RENDERER_OPENGL_H_
diff --git a/engine/src/flutter/shell/platform/linux/fl_view_renderer_software.cc b/engine/src/flutter/shell/platform/linux/fl_view_renderer_software.cc
new file mode 100644
index 0000000..03d8251
--- /dev/null
+++ b/engine/src/flutter/shell/platform/linux/fl_view_renderer_software.cc
@@ -0,0 +1,227 @@
+// 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.
+
+#include "flutter/shell/platform/linux/fl_view_renderer_software.h"
+
+#include "flutter/shell/platform/linux/fl_compositor_software.h"
+#include "flutter/shell/platform/linux/fl_engine_private.h"
+#include "flutter/shell/platform/linux/fl_task_runner.h"
+
+// Maximum time to wait for a frame to be ready before giving up and rendering.
+static constexpr gint64 kRenderTimeoutMicroseconds = 100000; // 100ms
+
+struct _FlViewRendererSoftware {
+ FlViewRenderer parent_instance;
+
+ // Engine this widget is rendering.
+ FlEngine* engine;
+
+ // TRUE if the view size should be controlled by Flutter.
+ gboolean sized_to_content;
+
+ // Combines and stores frames.
+ FlCompositorSoftware* compositor;
+
+ // Task runner to wait for frames on.
+ FlTaskRunner* task_runner;
+
+ // Ensure Flutter and GTK can access the frame stored in the compositor.
+ GMutex frame_mutex;
+};
+
+G_DEFINE_TYPE(FlViewRendererSoftware,
+ fl_view_renderer_software,
+ fl_view_renderer_get_type())
+
+// Redraw the view from the GTK thread.
+static gboolean redraw_cb(gpointer user_data) {
+ g_autoptr(FlViewRendererSoftware) self = FL_VIEW_RENDERER_SOFTWARE(user_data);
+
+ if (self->compositor == nullptr) {
+ return G_SOURCE_REMOVE;
+ }
+
+ fl_view_renderer_notify_frame(FL_VIEW_RENDERER(self));
+
+ // If Flutter is controlling the window size, then resize the view if
+ // necessary.
+ GtkWidget* render_widget = GTK_WIDGET(self);
+ GtkAllocation allocation;
+ gtk_widget_get_allocation(render_widget, &allocation);
+ gint scale_factor = gtk_widget_get_scale_factor(render_widget);
+ size_t width = allocation.width * scale_factor;
+ size_t height = allocation.height * scale_factor;
+ size_t frame_width, frame_height;
+ {
+ g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&self->frame_mutex);
+ fl_compositor_software_get_frame_size(self->compositor, &frame_width,
+ &frame_height);
+ }
+ gboolean frame_size_matches = width == frame_width && height == frame_height;
+ if (self->sized_to_content && !frame_size_matches) {
+ gtk_widget_set_size_request(render_widget, frame_width / scale_factor,
+ frame_height / scale_factor);
+ GtkWidget* toplevel = gtk_widget_get_toplevel(render_widget);
+ if (GTK_IS_WINDOW(toplevel)) {
+ // Resize to smallest size, so that the window will shrink to fit the new
+ // size of the render area.
+ gtk_window_resize(GTK_WINDOW(toplevel), 1, 1);
+ }
+ return G_SOURCE_REMOVE;
+ }
+
+ gtk_widget_queue_draw(render_widget);
+
+ return G_SOURCE_REMOVE;
+}
+
+// Wait for a frame matching the window size to be ready, or until the timeout
+// expires. Must be called with the frame mutex held; the mutex is still held
+// when this function returns.
+static void wait_for_frame(FlViewRendererSoftware* self,
+ GdkWindow* window,
+ gint scale_factor) {
+ gint64 expiry_time = g_get_monotonic_time() + kRenderTimeoutMicroseconds;
+ while (true) {
+ size_t width = gdk_window_get_width(window) * scale_factor;
+ size_t height = gdk_window_get_height(window) * scale_factor;
+ size_t frame_width, frame_height;
+ fl_compositor_software_get_frame_size(self->compositor, &frame_width,
+ &frame_height);
+ if (frame_width == width && frame_height == height) {
+ break;
+ }
+
+ if (g_get_monotonic_time() > expiry_time) {
+ g_warning(
+ "Timed out waiting for software frame of size %zdx%zd (have "
+ "%zdx%zd)",
+ width, height, frame_width, frame_height);
+ break;
+ }
+
+ g_mutex_unlock(&self->frame_mutex);
+ fl_task_runner_wait(self->task_runner, expiry_time);
+ g_mutex_lock(&self->frame_mutex);
+ }
+}
+
+// Implements GtkWidget::realize.
+static void fl_view_renderer_software_realize(GtkWidget* widget) {
+ FlViewRendererSoftware* self = FL_VIEW_RENDERER_SOFTWARE(widget);
+
+ GTK_WIDGET_CLASS(fl_view_renderer_software_parent_class)->realize(widget);
+
+ self->task_runner =
+ FL_TASK_RUNNER(g_object_ref(fl_engine_get_task_runner(self->engine)));
+ self->compositor = fl_compositor_software_new();
+}
+
+// Implements GtkWidget::draw.
+static gboolean fl_view_renderer_software_draw(GtkWidget* widget, cairo_t* cr) {
+ FlViewRendererSoftware* self = FL_VIEW_RENDERER_SOFTWARE(widget);
+
+ fl_view_renderer_paint_background(FL_VIEW_RENDERER(self), cr);
+
+ // The compositor is created when the widget is realized; if it is not yet
+ // available there is nothing to render beyond the background.
+ if (self->compositor == nullptr) {
+ return TRUE;
+ }
+
+ GdkWindow* window = gtk_widget_get_window(widget);
+ gint scale_factor = gdk_window_get_scale_factor(window);
+
+ g_mutex_lock(&self->frame_mutex);
+
+ // If frame not ready, then wait for it.
+ if (!self->sized_to_content) {
+ wait_for_frame(self, window, scale_factor);
+ }
+
+ gboolean result =
+ fl_compositor_software_render(self->compositor, cr, scale_factor);
+
+ g_mutex_unlock(&self->frame_mutex);
+
+ return result;
+}
+
+// Implements FlViewRenderer::present_layers.
+static void fl_view_renderer_software_present_layers(
+ FlViewRenderer* renderer,
+ const FlutterLayer** layers,
+ size_t layers_count) {
+ FlViewRendererSoftware* self = FL_VIEW_RENDERER_SOFTWARE(renderer);
+
+ // Frames may be presented before the widget is realized and the compositor
+ // is set up; ignore them.
+ if (self->compositor == nullptr) {
+ return;
+ }
+
+ g_mutex_lock(&self->frame_mutex);
+ fl_compositor_software_composite_layers(self->compositor, layers,
+ layers_count);
+ g_mutex_unlock(&self->frame_mutex);
+
+ // Wake up the GTK thread if it is waiting for this frame.
+ fl_task_runner_stop_wait(self->task_runner);
+
+ // Perform the redraw in the GTK thread.
+ g_idle_add(redraw_cb, g_object_ref(self));
+}
+
+static void fl_view_renderer_software_dispose(GObject* object) {
+ FlViewRendererSoftware* self = FL_VIEW_RENDERER_SOFTWARE(object);
+
+ g_clear_object(&self->engine);
+ g_clear_object(&self->task_runner);
+ g_mutex_clear(&self->frame_mutex);
+
+ G_OBJECT_CLASS(fl_view_renderer_software_parent_class)->dispose(object);
+}
+
+static void fl_view_renderer_software_finalize(GObject* object) {
+ FlViewRendererSoftware* self = FL_VIEW_RENDERER_SOFTWARE(object);
+
+ // The compositor is released here rather than in dispose() so it outlives a
+ // forced dispose (e.g. gtk_widget_destroy()) and is only freed once the last
+ // reference is dropped. This keeps it alive for the raster thread, which
+ // holds a strong reference on the view (and thus this renderer) while
+ // presenting.
+ g_clear_object(&self->compositor);
+
+ G_OBJECT_CLASS(fl_view_renderer_software_parent_class)->finalize(object);
+}
+
+static void fl_view_renderer_software_class_init(
+ FlViewRendererSoftwareClass* klass) {
+ GObjectClass* object_class = G_OBJECT_CLASS(klass);
+ object_class->dispose = fl_view_renderer_software_dispose;
+ object_class->finalize = fl_view_renderer_software_finalize;
+
+ GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
+ widget_class->realize = fl_view_renderer_software_realize;
+ widget_class->draw = fl_view_renderer_software_draw;
+
+ FlViewRendererClass* renderer_class = FL_VIEW_RENDERER_CLASS(klass);
+ renderer_class->present_layers = fl_view_renderer_software_present_layers;
+}
+
+static void fl_view_renderer_software_init(FlViewRendererSoftware* self) {
+ g_mutex_init(&self->frame_mutex);
+}
+
+FlViewRendererSoftware* fl_view_renderer_software_new(
+ FlEngine* engine,
+ gboolean sized_to_content) {
+ g_return_val_if_fail(FL_IS_ENGINE(engine), nullptr);
+
+ FlViewRendererSoftware* self = FL_VIEW_RENDERER_SOFTWARE(
+ g_object_new(fl_view_renderer_software_get_type(), nullptr));
+ self->engine = FL_ENGINE(g_object_ref(engine));
+ self->sized_to_content = sized_to_content;
+ return self;
+}
diff --git a/engine/src/flutter/shell/platform/linux/fl_view_renderer_software.h b/engine/src/flutter/shell/platform/linux/fl_view_renderer_software.h
new file mode 100644
index 0000000..534b3ba
--- /dev/null
+++ b/engine/src/flutter/shell/platform/linux/fl_view_renderer_software.h
@@ -0,0 +1,41 @@
+// 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_FL_VIEW_RENDERER_SOFTWARE_H_
+#define FLUTTER_SHELL_PLATFORM_LINUX_FL_VIEW_RENDERER_SOFTWARE_H_
+
+#include "flutter/shell/platform/linux/fl_view_renderer.h"
+#include "flutter/shell/platform/linux/public/flutter_linux/fl_engine.h"
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE(FlViewRendererSoftware,
+ fl_view_renderer_software,
+ FL,
+ VIEW_RENDERER_SOFTWARE,
+ FlViewRenderer)
+
+/**
+ * FlViewRendererSoftware:
+ *
+ * #FlViewRendererSoftware is an #FlViewRenderer that renders Flutter frames
+ * using software rendering.
+ */
+
+/**
+ * fl_view_renderer_software_new:
+ * @engine: the #FlEngine to render.
+ * @sized_to_content: %TRUE if the view size is controlled by Flutter.
+ *
+ * Creates a new widget that renders Flutter frames using software rendering.
+ *
+ * Returns: a new #FlViewRendererSoftware.
+ */
+FlViewRendererSoftware* fl_view_renderer_software_new(
+ FlEngine* engine,
+ gboolean sized_to_content);
+
+G_END_DECLS
+
+#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_VIEW_RENDERER_SOFTWARE_H_