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_