Add EGL Surface backing store (#43683)

Allows using an EGL surface as a flutter backing store. Way more convenient for GBM than hacking gbm bo's into GL FBOs.

This resolves https://github.com/flutter/flutter/issues/58363

Currently, the embedder API assumes that the compositor (if it exists) will let flutter render into FBOs or Textures and then composite the whole thing onto the actual (EGL) window surface. I think this assumption is also documented a bit in https://github.com/flutter/flutter/issues/38466

However, in my case, I want let the hardware do the composition (using the linux KMS API), and render each flutter layer into it's own EGL surface.

It's possible to hack around this by creating your own GBM BOs, importing those as EGL images, then importing those as GL Render Buffers and attaching those to GL FBOs and that works (tested it). However, that's basically reimplementing 50% of the whole GBM/EGL "window" system integration for no reason.

This PR adds:
1. To the embedder API:
   - a new kind of OpenGL Backing store: `FlutterOpenGLSurface`
     - consisting of just a `make_current` and destruction callback (plus userdata)
     - the make_current callback should make the target surface current, i.e. `eglMakeCurrent(..., surf, surf)`
     - will be called by the engine before rendering onto the backing store
2. Some wiring to call make_current before rendering into the backing store

## TODO:

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc
index ba2859a..81bd357 100644
--- a/shell/platform/embedder/embedder.cc
+++ b/shell/platform/embedder/embedder.cc
@@ -279,6 +279,13 @@
                   static_cast<int32_t>(flutter_rect.bottom)};
   return rect;
 }
+
+// We need GL_BGRA8_EXT for creating SkSurfaces from FlutterOpenGLSurfaces
+// below.
+#ifndef GL_BGRA8_EXT
+#define GL_BGRA8_EXT 0x93A1
+#endif
+
 #endif
 
 static inline flutter::Shell::CreateCallback<flutter::PlatformView>
@@ -830,6 +837,47 @@
 static sk_sp<SkSurface> MakeSkSurfaceFromBackingStore(
     GrDirectContext* context,
     const FlutterBackingStoreConfig& config,
+    const FlutterOpenGLSurface* surface) {
+#ifdef SHELL_ENABLE_GL
+  GrGLFramebufferInfo framebuffer_info = {};
+  framebuffer_info.fFormat = GL_BGRA8_EXT;
+  framebuffer_info.fFBOID = 0;
+
+  auto backend_render_target =
+      GrBackendRenderTargets::MakeGL(config.size.width,   // width
+                                     config.size.height,  // height
+                                     1,                   // sample count
+                                     0,                   // stencil bits
+                                     framebuffer_info     // framebuffer info
+      );
+
+  SkSurfaceProps surface_properties(0, kUnknown_SkPixelGeometry);
+
+  auto sk_surface = SkSurfaces::WrapBackendRenderTarget(
+      context,                      //  context
+      backend_render_target,        // backend render target
+      kBottomLeft_GrSurfaceOrigin,  // surface origin
+      kN32_SkColorType,             // color type
+      SkColorSpace::MakeSRGB(),     // color space
+      &surface_properties,          // surface properties
+      static_cast<SkSurfaces::RenderTargetReleaseProc>(
+          surface->destruction_callback),  // release proc
+      surface->user_data                   // release context
+  );
+
+  if (!sk_surface) {
+    FML_LOG(ERROR) << "Could not wrap embedder supplied frame-buffer.";
+    return nullptr;
+  }
+  return sk_surface;
+#else
+  return nullptr;
+#endif
+}
+
+static sk_sp<SkSurface> MakeSkSurfaceFromBackingStore(
+    GrDirectContext* context,
+    const FlutterBackingStoreConfig& config,
     const FlutterSoftwareBackingStore* software) {
   const auto image_info =
       SkImageInfo::MakeN32Premul(config.size.width, config.size.height);
@@ -1154,14 +1202,27 @@
 }
 
 static std::unique_ptr<flutter::EmbedderRenderTarget>
-MakeRenderTargetFromSkSurface(FlutterBackingStore backing_store,
-                              sk_sp<SkSurface> skia_surface,
-                              fml::closure on_release) {
+MakeRenderTargetFromSkSurface(
+    FlutterBackingStore backing_store,
+    sk_sp<SkSurface> skia_surface,
+    fml::closure on_release,
+    flutter::EmbedderRenderTarget::MakeOrClearCurrentCallback on_make_current,
+    flutter::EmbedderRenderTarget::MakeOrClearCurrentCallback
+        on_clear_current) {
   if (!skia_surface) {
     return nullptr;
   }
   return std::make_unique<flutter::EmbedderRenderTargetSkia>(
-      backing_store, std::move(skia_surface), std::move(on_release));
+      backing_store, std::move(skia_surface), std::move(on_release),
+      std::move(on_make_current), std::move(on_clear_current));
+}
+
+static std::unique_ptr<flutter::EmbedderRenderTarget>
+MakeRenderTargetFromSkSurface(FlutterBackingStore backing_store,
+                              sk_sp<SkSurface> skia_surface,
+                              fml::closure on_release) {
+  return MakeRenderTargetFromSkSurface(backing_store, std::move(skia_surface),
+                                       std::move(on_release), nullptr, nullptr);
 }
 
 static std::unique_ptr<flutter::EmbedderRenderTarget>
@@ -1233,9 +1294,45 @@
             break;
           }
         }
+
+        case kFlutterOpenGLTargetTypeSurface: {
+          auto on_make_current =
+              [callback = backing_store.open_gl.surface.make_current_callback,
+               context = backing_store.open_gl.surface.user_data]()
+              -> flutter::EmbedderRenderTarget::SetCurrentResult {
+            bool invalidate_api_state = false;
+            bool ok = callback(context, &invalidate_api_state);
+            return {ok, invalidate_api_state};
+          };
+
+          auto on_clear_current =
+              [callback = backing_store.open_gl.surface.clear_current_callback,
+               context = backing_store.open_gl.surface.user_data]()
+              -> flutter::EmbedderRenderTarget::SetCurrentResult {
+            bool invalidate_api_state = false;
+            bool ok = callback(context, &invalidate_api_state);
+            return {ok, invalidate_api_state};
+          };
+
+          if (enable_impeller) {
+            // TODO(https://github.com/flutter/flutter/issues/151670): Implement
+            //  GL Surface backing stores for Impeller.
+            FML_LOG(ERROR) << "Unimplemented";
+            break;
+          } else {
+            auto skia_surface = MakeSkSurfaceFromBackingStore(
+                context, config, &backing_store.open_gl.surface);
+
+            render_target = MakeRenderTargetFromSkSurface(
+                backing_store, std::move(skia_surface),
+                collect_callback.Release(), on_make_current, on_clear_current);
+            break;
+          }
+        }
       }
       break;
     }
+
     case kFlutterBackingStoreTypeSoftware: {
       auto skia_surface = MakeSkSurfaceFromBackingStore(
           context, config, &backing_store.software);
diff --git a/shell/platform/embedder/embedder.h b/shell/platform/embedder/embedder.h
index 969af06..6c59c2c 100644
--- a/shell/platform/embedder/embedder.h
+++ b/shell/platform/embedder/embedder.h
@@ -304,6 +304,9 @@
   /// Specifies an OpenGL frame-buffer target type. Framebuffers are specified
   /// using the FlutterOpenGLFramebuffer struct.
   kFlutterOpenGLTargetTypeFramebuffer,
+  /// Specifies an OpenGL on-screen surface target type. Surfaces are specified
+  /// using the FlutterOpenGLSurface struct.
+  kFlutterOpenGLTargetTypeSurface,
 } FlutterOpenGLTargetType;
 
 /// A pixel format to be used for software rendering.
@@ -408,6 +411,55 @@
   VoidCallback destruction_callback;
 } FlutterOpenGLFramebuffer;
 
+typedef bool (*FlutterOpenGLSurfaceCallback)(void* /* user data */,
+                                             bool* /* opengl state changed */);
+
+typedef struct {
+  /// The size of this struct. Must be sizeof(FlutterOpenGLSurface).
+  size_t struct_size;
+
+  /// User data to be passed to the make_current, clear_current and
+  /// destruction callbacks.
+  void* user_data;
+
+  /// Callback invoked (on an engine-managed thread) that asks the embedder to
+  /// make the surface current.
+  ///
+  /// Should return true if the operation succeeded, false if the surface could
+  /// not be made current and rendering should be cancelled.
+  ///
+  /// The second parameter 'opengl state changed' should be set to true if
+  /// any OpenGL API state is different than before this callback was called.
+  /// In that case, Flutter will invalidate the internal OpenGL API state cache,
+  /// which is a somewhat expensive operation.
+  ///
+  /// @attention required. (non-null)
+  FlutterOpenGLSurfaceCallback make_current_callback;
+
+  /// Callback invoked (on an engine-managed thread) when the current surface
+  /// can be cleared.
+  ///
+  /// Should return true if the operation succeeded, false if an error ocurred.
+  /// That error will be logged but otherwise not handled by the engine.
+  ///
+  /// The second parameter 'opengl state changed' is the same as with the
+  /// @ref make_current_callback.
+  ///
+  /// The embedder might clear the surface here after it was previously made
+  /// current. That's not required however, it's also possible to clear it in
+  /// the destruction callback. There's no way to signal OpenGL state
+  /// changes in the destruction callback though.
+  ///
+  /// @attention required. (non-null)
+  FlutterOpenGLSurfaceCallback clear_current_callback;
+
+  /// Callback invoked (on an engine-managed thread) that asks the embedder to
+  /// collect the surface.
+  ///
+  /// @attention required. (non-null)
+  VoidCallback destruction_callback;
+} FlutterOpenGLSurface;
+
 typedef bool (*BoolCallback)(void* /* user data */);
 typedef FlutterTransformation (*TransformationCallback)(void* /* user data */);
 typedef uint32_t (*UIntCallback)(void* /* user data */);
@@ -1619,6 +1671,9 @@
     /// A framebuffer for Flutter to render into. The embedder must ensure that
     /// the framebuffer is complete.
     FlutterOpenGLFramebuffer framebuffer;
+    /// A surface for Flutter to render into. Basically a wrapper around
+    /// a closure that'll be called when the surface should be made current.
+    FlutterOpenGLSurface surface;
   };
 } FlutterOpenGLBackingStore;
 
diff --git a/shell/platform/embedder/embedder_external_view.cc b/shell/platform/embedder/embedder_external_view.cc
index c7806ff..349f77f 100644
--- a/shell/platform/embedder/embedder_external_view.cc
+++ b/shell/platform/embedder/embedder_external_view.cc
@@ -7,6 +7,8 @@
 #include "flutter/display_list/dl_builder.h"
 #include "flutter/fml/trace_event.h"
 #include "flutter/shell/common/dl_op_spy.h"
+#include "third_party/skia/include/gpu/GrDirectContext.h"
+#include "third_party/skia/include/gpu/GrRecordingContext.h"
 
 #ifdef IMPELLER_SUPPORTS_RENDERING
 #include "impeller/display_list/dl_dispatcher.h"  // nogncheck
@@ -88,6 +90,27 @@
   return embedded_view_params_.get();
 }
 
+// TODO(https://github.com/flutter/flutter/issues/151670): Implement this for
+//  Impeller as well.
+#if !SLIMPELLER
+static void InvalidateApiState(SkSurface& skia_surface) {
+  auto recording_context = skia_surface.recordingContext();
+
+  // Should never happen.
+  FML_DCHECK(recording_context) << "Recording context was null.";
+
+  auto direct_context = recording_context->asDirectContext();
+  if (direct_context == nullptr) {
+    // Can happen when using software rendering.
+    // Print an error but otherwise continue in that case.
+    FML_LOG(ERROR) << "Embedder asked to invalidate cached graphics API state "
+                      "but Flutter is not using a graphics API.";
+  } else {
+    direct_context->resetContext(kAll_GrBackendState);
+  }
+}
+#endif
+
 bool EmbedderExternalView::Render(const EmbedderRenderTarget& render_target,
                                   bool clear_surface) {
   TRACE_EVENT0("flutter", "EmbedderExternalView::Render");
@@ -143,6 +166,28 @@
     return false;
   }
 
+  auto [ok, invalidate_api_state] = render_target.MaybeMakeCurrent();
+
+  if (invalidate_api_state) {
+    InvalidateApiState(*skia_surface);
+  }
+  if (!ok) {
+    FML_LOG(ERROR) << "Could not make the surface current.";
+    return false;
+  }
+
+  // Clear the current render target (most likely EGLSurface) at the
+  // end of this scope.
+  fml::ScopedCleanupClosure clear_current_surface([&]() {
+    auto [ok, invalidate_api_state] = render_target.MaybeClearCurrent();
+    if (invalidate_api_state) {
+      InvalidateApiState(*skia_surface);
+    }
+    if (!ok) {
+      FML_LOG(ERROR) << "Could not clear the current surface.";
+    }
+  });
+
   FML_DCHECK(render_target.GetRenderTargetSize() == render_surface_size_);
 
   auto canvas = skia_surface->getCanvas();
diff --git a/shell/platform/embedder/embedder_render_target.h b/shell/platform/embedder/embedder_render_target.h
index cfbb67c..245ec85 100644
--- a/shell/platform/embedder/embedder_render_target.h
+++ b/shell/platform/embedder/embedder_render_target.h
@@ -27,6 +27,20 @@
 ///
 class EmbedderRenderTarget {
  public:
+  struct SetCurrentResult {
+    /// This is true if the operation succeeded (even if it was a no-op),
+    /// false if the surface could not be made current / the current surface
+    /// could not be cleared.
+    bool success;
+
+    /// This is true if any native graphics API (e.g. GL, but not EGL) state has
+    /// changed and Skia/Impeller should not assume any GL state values are the
+    /// same as before the context change operation was attempted.
+    bool gl_state_trampled;
+  };
+
+  using MakeOrClearCurrentCallback = std::function<SetCurrentResult()>;
+
   //----------------------------------------------------------------------------
   /// @brief      Destroys this instance of the render target and invokes the
   ///             callback for the embedder to release its resource associated
@@ -77,6 +91,24 @@
   ///
   const FlutterBackingStore* GetBackingStore() const;
 
+  //----------------------------------------------------------------------------
+  /// @brief      Make the render target current.
+  ///
+  ///             Sometimes render targets are actually (for example)
+  ///             EGL surfaces instead of framebuffers or textures.
+  ///             In that case, we can't fully wrap them as SkSurfaces, instead,
+  ///             the embedder will provide a callback that should be called
+  ///             when the target surface should be made current.
+  ///
+  /// @return     The result of the operation.
+  virtual SetCurrentResult MaybeMakeCurrent() const { return {true, false}; }
+
+  //----------------------------------------------------------------------------
+  /// @brief      Clear the current render target. @see MaybeMakeCurrent
+  ///
+  /// @return     The result of the operation.
+  virtual SetCurrentResult MaybeClearCurrent() const { return {true, false}; }
+
  protected:
   //----------------------------------------------------------------------------
   /// @brief      Creates a render target whose backing store is managed by the
diff --git a/shell/platform/embedder/embedder_render_target_skia.cc b/shell/platform/embedder/embedder_render_target_skia.cc
index e8d60ca..25bc927 100644
--- a/shell/platform/embedder/embedder_render_target_skia.cc
+++ b/shell/platform/embedder/embedder_render_target_skia.cc
@@ -11,9 +11,13 @@
 EmbedderRenderTargetSkia::EmbedderRenderTargetSkia(
     FlutterBackingStore backing_store,
     sk_sp<SkSurface> render_surface,
-    fml::closure on_release)
+    fml::closure on_release,
+    MakeOrClearCurrentCallback on_make_current,
+    MakeOrClearCurrentCallback on_clear_current)
     : EmbedderRenderTarget(backing_store, std::move(on_release)),
-      render_surface_(std::move(render_surface)) {
+      render_surface_(std::move(render_surface)),
+      on_make_current_(std::move(on_make_current)),
+      on_clear_current_(std::move(on_clear_current)) {
   FML_DCHECK(render_surface_);
 }
 
@@ -37,4 +41,22 @@
   return SkISize::Make(render_surface_->width(), render_surface_->height());
 }
 
+EmbedderRenderTarget::SetCurrentResult
+EmbedderRenderTargetSkia::MaybeMakeCurrent() const {
+  if (on_make_current_ != nullptr) {
+    return on_make_current_();
+  }
+
+  return {true, false};
+}
+
+EmbedderRenderTarget::SetCurrentResult
+EmbedderRenderTargetSkia::MaybeClearCurrent() const {
+  if (on_clear_current_ != nullptr) {
+    return on_clear_current_();
+  }
+
+  return {true, false};
+}
+
 }  // namespace flutter
diff --git a/shell/platform/embedder/embedder_render_target_skia.h b/shell/platform/embedder/embedder_render_target_skia.h
index 2ccacfe..6331f21 100644
--- a/shell/platform/embedder/embedder_render_target_skia.h
+++ b/shell/platform/embedder/embedder_render_target_skia.h
@@ -13,7 +13,9 @@
  public:
   EmbedderRenderTargetSkia(FlutterBackingStore backing_store,
                            sk_sp<SkSurface> render_surface,
-                           fml::closure on_release);
+                           fml::closure on_release,
+                           MakeOrClearCurrentCallback on_make_current,
+                           MakeOrClearCurrentCallback on_clear_current);
 
   // |EmbedderRenderTarget|
   ~EmbedderRenderTargetSkia() override;
@@ -30,9 +32,18 @@
   // |EmbedderRenderTarget|
   SkISize GetRenderTargetSize() const override;
 
+  // |EmbedderRenderTarget|
+  SetCurrentResult MaybeMakeCurrent() const override;
+
+  // |EmbedderRenderTarget|
+  SetCurrentResult MaybeClearCurrent() const override;
+
  private:
   sk_sp<SkSurface> render_surface_;
 
+  MakeOrClearCurrentCallback on_make_current_;
+  MakeOrClearCurrentCallback on_clear_current_;
+
   FML_DISALLOW_COPY_AND_ASSIGN(EmbedderRenderTargetSkia);
 };
 
diff --git a/shell/platform/embedder/tests/embedder_assertions.h b/shell/platform/embedder/tests/embedder_assertions.h
index 09b0d48..8145602 100644
--- a/shell/platform/embedder/tests/embedder_assertions.h
+++ b/shell/platform/embedder/tests/embedder_assertions.h
@@ -67,6 +67,13 @@
          a.destruction_callback == b.destruction_callback;
 }
 
+inline bool operator==(const FlutterOpenGLSurface& a,
+                       const FlutterOpenGLSurface& b) {
+  return a.make_current_callback == b.make_current_callback &&
+         a.user_data == b.user_data &&
+         a.destruction_callback == b.destruction_callback;
+}
+
 inline bool operator==(const FlutterMetalTexture& a,
                        const FlutterMetalTexture& b) {
   return a.texture_id == b.texture_id && a.texture == b.texture;
@@ -98,6 +105,8 @@
       return a.texture == b.texture;
     case kFlutterOpenGLTargetTypeFramebuffer:
       return a.framebuffer == b.framebuffer;
+    case kFlutterOpenGLTargetTypeSurface:
+      return a.surface == b.surface;
   }
 
   return false;
@@ -297,6 +306,14 @@
 }
 
 inline std::ostream& operator<<(std::ostream& out,
+                                const FlutterOpenGLSurface& item) {
+  return out << "(FlutterOpenGLSurface) Make Current Callback: "
+             << reinterpret_cast<void*>(item.make_current_callback)
+             << " User Data: " << item.user_data << " Destruction Callback: "
+             << reinterpret_cast<void*>(item.destruction_callback);
+}
+
+inline std::ostream& operator<<(std::ostream& out,
                                 const FlutterMetalTexture& item) {
   return out << "(FlutterMetalTexture) Texture ID: " << std::hex
              << item.texture_id << std::dec << " Handle: 0x" << std::hex
@@ -368,6 +385,8 @@
       return "kFlutterOpenGLTargetTypeTexture";
     case kFlutterOpenGLTargetTypeFramebuffer:
       return "kFlutterOpenGLTargetTypeFramebuffer";
+    case kFlutterOpenGLTargetTypeSurface:
+      return "kFlutterOpenGLTargetTypeSurface";
   }
   return "Unknown";
 }
@@ -406,6 +425,9 @@
     case kFlutterOpenGLTargetTypeFramebuffer:
       out << item.framebuffer;
       break;
+    case kFlutterOpenGLTargetTypeSurface:
+      out << item.surface;
+      break;
   }
   return out;
 }
diff --git a/shell/platform/embedder/tests/embedder_config_builder.cc b/shell/platform/embedder/tests/embedder_config_builder.cc
index fa89c66..3d39cca 100644
--- a/shell/platform/embedder/tests/embedder_config_builder.cc
+++ b/shell/platform/embedder/tests/embedder_config_builder.cc
@@ -407,11 +407,17 @@
     EmbedderTestBackingStoreProducer::RenderTargetType type,
     FlutterSoftwarePixelFormat software_pixfmt) {
   auto& compositor = context_.GetCompositor();
+
+  auto producer = std::make_unique<EmbedderTestBackingStoreProducer>(
+      compositor.GetGrContext(), type, software_pixfmt);
+
+#ifdef SHELL_ENABLE_GL
+  producer->SetEGLContext(context_.egl_context_);
+#endif
+
   // TODO(wrightgeorge): figure out a better way of plumbing through the
   // GrDirectContext
-  compositor.SetBackingStoreProducer(
-      std::make_unique<EmbedderTestBackingStoreProducer>(
-          compositor.GetGrContext(), type, software_pixfmt));
+  compositor.SetBackingStoreProducer(std::move(producer));
 }
 
 UniqueEngine EmbedderConfigBuilder::LaunchEngine() const {
diff --git a/shell/platform/embedder/tests/embedder_gl_unittests.cc b/shell/platform/embedder/tests/embedder_gl_unittests.cc
index 0d01bc1..54ca779 100644
--- a/shell/platform/embedder/tests/embedder_gl_unittests.cc
+++ b/shell/platform/embedder/tests/embedder_gl_unittests.cc
@@ -4806,6 +4806,332 @@
   ASSERT_FALSE(present_called);
 }
 
+TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToOpenGLSurface) {
+  auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext);
+
+  EmbedderConfigBuilder builder(context);
+  builder.SetOpenGLRendererConfig(SkISize::Make(800, 600));
+  builder.SetCompositor();
+  builder.SetDartEntrypoint("can_composite_platform_views");
+
+  builder.SetRenderTargetType(
+      EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLSurface);
+
+  fml::CountDownLatch latch(3);
+  context.GetCompositor().SetNextPresentCallback(
+      [&](FlutterViewId view_id, const FlutterLayer** layers,
+          size_t layers_count) {
+        ASSERT_EQ(layers_count, 3u);
+
+        {
+          FlutterBackingStore backing_store = *layers[0]->backing_store;
+          backing_store.struct_size = sizeof(backing_store);
+          backing_store.type = kFlutterBackingStoreTypeOpenGL;
+          backing_store.did_update = true;
+          backing_store.open_gl.type = kFlutterOpenGLTargetTypeSurface;
+
+          FlutterRect paint_region_rects[] = {
+              FlutterRectMakeLTRB(0, 0, 800, 600),
+          };
+          FlutterRegion paint_region = {
+              .struct_size = sizeof(FlutterRegion),
+              .rects_count = 1,
+              .rects = paint_region_rects,
+          };
+          FlutterBackingStorePresentInfo present_info = {
+              .struct_size = sizeof(FlutterBackingStorePresentInfo),
+              .paint_region = &paint_region,
+          };
+
+          FlutterLayer layer = {};
+          layer.struct_size = sizeof(layer);
+          layer.type = kFlutterLayerContentTypeBackingStore;
+          layer.backing_store = &backing_store;
+          layer.size = FlutterSizeMake(800.0, 600.0);
+          layer.offset = FlutterPointMake(0, 0);
+          layer.backing_store_present_info = &present_info;
+          ASSERT_EQ(*layers[0], layer);
+        }
+
+        {
+          FlutterPlatformView platform_view = *layers[1]->platform_view;
+          platform_view.struct_size = sizeof(platform_view);
+          platform_view.identifier = 42;
+
+          FlutterLayer layer = {};
+          layer.struct_size = sizeof(layer);
+          layer.type = kFlutterLayerContentTypePlatformView;
+          layer.platform_view = &platform_view;
+          layer.size = FlutterSizeMake(123.0, 456.0);
+          layer.offset = FlutterPointMake(1.0, 2.0);
+
+          ASSERT_EQ(*layers[1], layer);
+        }
+
+        {
+          FlutterBackingStore backing_store = *layers[2]->backing_store;
+          backing_store.struct_size = sizeof(backing_store);
+          backing_store.type = kFlutterBackingStoreTypeOpenGL;
+          backing_store.did_update = true;
+          backing_store.open_gl.type = kFlutterOpenGLTargetTypeSurface;
+
+          FlutterRect paint_region_rects[] = {
+              FlutterRectMakeLTRB(2, 3, 800, 600),
+          };
+          FlutterRegion paint_region = {
+              .struct_size = sizeof(FlutterRegion),
+              .rects_count = 1,
+              .rects = paint_region_rects,
+          };
+          FlutterBackingStorePresentInfo present_info = {
+              .struct_size = sizeof(FlutterBackingStorePresentInfo),
+              .paint_region = &paint_region,
+          };
+
+          FlutterLayer layer = {};
+          layer.struct_size = sizeof(layer);
+          layer.type = kFlutterLayerContentTypeBackingStore;
+          layer.backing_store = &backing_store;
+          layer.size = FlutterSizeMake(800.0, 600.0);
+          layer.offset = FlutterPointMake(0.0, 0.0);
+          layer.backing_store_present_info = &present_info;
+
+          ASSERT_EQ(*layers[2], layer);
+        }
+
+        latch.CountDown();
+      });
+
+  context.AddNativeCallback(
+      "SignalNativeTest",
+      CREATE_NATIVE_ENTRY(
+          [&latch](Dart_NativeArguments args) { latch.CountDown(); }));
+
+  auto engine = builder.LaunchEngine();
+
+  // Send a window metrics events so frames may be scheduled.
+  FlutterWindowMetricsEvent event = {};
+  event.struct_size = sizeof(event);
+  event.width = 800;
+  event.height = 600;
+  event.pixel_ratio = 1.0;
+  ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event),
+            kSuccess);
+  ASSERT_TRUE(engine.is_valid());
+
+  latch.Wait();
+}
+
+TEST_F(EmbedderTest, CompositorMustBeAbleToRenderKnownSceneToOpenGLSurfaces) {
+  auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext);
+
+  EmbedderConfigBuilder builder(context);
+  builder.SetOpenGLRendererConfig(SkISize::Make(800, 600));
+  builder.SetCompositor();
+  builder.SetDartEntrypoint("can_composite_platform_views_with_known_scene");
+
+  builder.SetRenderTargetType(
+      EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLSurface);
+
+  fml::CountDownLatch latch(5);
+
+  auto scene_image = context.GetNextSceneImage();
+
+  context.GetCompositor().SetNextPresentCallback(
+      [&](FlutterViewId view_id, const FlutterLayer** layers,
+          size_t layers_count) {
+        ASSERT_EQ(layers_count, 5u);
+
+        // Layer Root
+        {
+          FlutterBackingStore backing_store = *layers[0]->backing_store;
+          backing_store.type = kFlutterBackingStoreTypeOpenGL;
+          backing_store.did_update = true;
+          backing_store.open_gl.type = kFlutterOpenGLTargetTypeSurface;
+
+          FlutterRect paint_region_rects[] = {
+              FlutterRectMakeLTRB(0, 0, 800, 600),
+          };
+          FlutterRegion paint_region = {
+              .struct_size = sizeof(FlutterRegion),
+              .rects_count = 1,
+              .rects = paint_region_rects,
+          };
+          FlutterBackingStorePresentInfo present_info = {
+              .struct_size = sizeof(FlutterBackingStorePresentInfo),
+              .paint_region = &paint_region,
+          };
+
+          FlutterLayer layer = {};
+          layer.struct_size = sizeof(layer);
+          layer.type = kFlutterLayerContentTypeBackingStore;
+          layer.backing_store = &backing_store;
+          layer.size = FlutterSizeMake(800.0, 600.0);
+          layer.offset = FlutterPointMake(0.0, 0.0);
+          layer.backing_store_present_info = &present_info;
+
+          ASSERT_EQ(*layers[0], layer);
+        }
+
+        // Layer 1
+        {
+          FlutterPlatformView platform_view = *layers[1]->platform_view;
+          platform_view.struct_size = sizeof(platform_view);
+          platform_view.identifier = 1;
+
+          FlutterLayer layer = {};
+          layer.struct_size = sizeof(layer);
+          layer.type = kFlutterLayerContentTypePlatformView;
+          layer.platform_view = &platform_view;
+          layer.size = FlutterSizeMake(50.0, 150.0);
+          layer.offset = FlutterPointMake(20.0, 20.0);
+
+          ASSERT_EQ(*layers[1], layer);
+        }
+
+        // Layer 2
+        {
+          FlutterBackingStore backing_store = *layers[2]->backing_store;
+          backing_store.type = kFlutterBackingStoreTypeOpenGL;
+          backing_store.did_update = true;
+          backing_store.open_gl.type = kFlutterOpenGLTargetTypeSurface;
+
+          FlutterRect paint_region_rects[] = {
+              FlutterRectMakeLTRB(30, 30, 80, 180),
+          };
+          FlutterRegion paint_region = {
+              .struct_size = sizeof(FlutterRegion),
+              .rects_count = 1,
+              .rects = paint_region_rects,
+          };
+          FlutterBackingStorePresentInfo present_info = {
+              .struct_size = sizeof(FlutterBackingStorePresentInfo),
+              .paint_region = &paint_region,
+          };
+
+          FlutterLayer layer = {};
+          layer.struct_size = sizeof(layer);
+          layer.type = kFlutterLayerContentTypeBackingStore;
+          layer.backing_store = &backing_store;
+          layer.size = FlutterSizeMake(800.0, 600.0);
+          layer.offset = FlutterPointMake(0.0, 0.0);
+          layer.backing_store_present_info = &present_info;
+
+          ASSERT_EQ(*layers[2], layer);
+        }
+
+        // Layer 3
+        {
+          FlutterPlatformView platform_view = *layers[3]->platform_view;
+          platform_view.struct_size = sizeof(platform_view);
+          platform_view.identifier = 2;
+
+          FlutterLayer layer = {};
+          layer.struct_size = sizeof(layer);
+          layer.type = kFlutterLayerContentTypePlatformView;
+          layer.platform_view = &platform_view;
+          layer.size = FlutterSizeMake(50.0, 150.0);
+          layer.offset = FlutterPointMake(40.0, 40.0);
+
+          ASSERT_EQ(*layers[3], layer);
+        }
+
+        // Layer 4
+        {
+          FlutterBackingStore backing_store = *layers[4]->backing_store;
+          backing_store.type = kFlutterBackingStoreTypeOpenGL;
+          backing_store.did_update = true;
+          backing_store.open_gl.type = kFlutterOpenGLTargetTypeSurface;
+
+          FlutterRect paint_region_rects[] = {
+              FlutterRectMakeLTRB(50, 50, 100, 200),
+          };
+          FlutterRegion paint_region = {
+              .struct_size = sizeof(FlutterRegion),
+              .rects_count = 1,
+              .rects = paint_region_rects,
+          };
+          FlutterBackingStorePresentInfo present_info = {
+              .struct_size = sizeof(FlutterBackingStorePresentInfo),
+              .paint_region = &paint_region,
+          };
+
+          FlutterLayer layer = {};
+          layer.struct_size = sizeof(layer);
+          layer.type = kFlutterLayerContentTypeBackingStore;
+          layer.backing_store = &backing_store;
+          layer.size = FlutterSizeMake(800.0, 600.0);
+          layer.offset = FlutterPointMake(0.0, 0.0);
+          layer.backing_store_present_info = &present_info;
+
+          ASSERT_EQ(*layers[4], layer);
+        }
+
+        latch.CountDown();
+      });
+
+  context.GetCompositor().SetPlatformViewRendererCallback(
+      [&](const FlutterLayer& layer,
+          GrDirectContext* context) -> sk_sp<SkImage> {
+        auto surface = CreateRenderSurface(layer, context);
+        auto canvas = surface->getCanvas();
+        FML_CHECK(canvas != nullptr);
+
+        switch (layer.platform_view->identifier) {
+          case 1: {
+            SkPaint paint;
+            // See dart test for total order.
+            paint.setColor(SK_ColorGREEN);
+            paint.setAlpha(127);
+            const auto& rect =
+                SkRect::MakeWH(layer.size.width, layer.size.height);
+            canvas->drawRect(rect, paint);
+            latch.CountDown();
+          } break;
+          case 2: {
+            SkPaint paint;
+            // See dart test for total order.
+            paint.setColor(SK_ColorMAGENTA);
+            paint.setAlpha(127);
+            const auto& rect =
+                SkRect::MakeWH(layer.size.width, layer.size.height);
+            canvas->drawRect(rect, paint);
+            latch.CountDown();
+          } break;
+          default:
+            // Asked to render an unknown platform view.
+            FML_CHECK(false)
+                << "Test was asked to composite an unknown platform view.";
+        }
+
+        return surface->makeImageSnapshot();
+      });
+
+  context.AddNativeCallback(
+      "SignalNativeTest",
+      CREATE_NATIVE_ENTRY(
+          [&latch](Dart_NativeArguments args) { latch.CountDown(); }));
+
+  auto engine = builder.LaunchEngine();
+
+  // Send a window metrics events so frames may be scheduled.
+  FlutterWindowMetricsEvent event = {};
+  event.struct_size = sizeof(event);
+  event.width = 800;
+  event.height = 600;
+  event.pixel_ratio = 1.0;
+  ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event),
+            kSuccess);
+  ASSERT_TRUE(engine.is_valid());
+
+  latch.Wait();
+
+  ASSERT_TRUE(ImageMatchesFixture("compositor.png", scene_image));
+
+  // There should no present calls on the root surface.
+  ASSERT_EQ(context.GetSurfacePresentCount(), 0u);
+}
+
 INSTANTIATE_TEST_SUITE_P(
     EmbedderTestGlVk,
     EmbedderTestMultiBackend,
diff --git a/shell/platform/embedder/tests/embedder_test_backingstore_producer.cc b/shell/platform/embedder/tests/embedder_test_backingstore_producer.cc
index ac6959c..c93838a 100644
--- a/shell/platform/embedder/tests/embedder_test_backingstore_producer.cc
+++ b/shell/platform/embedder/tests/embedder_test_backingstore_producer.cc
@@ -29,7 +29,11 @@
 #ifdef SHELL_ENABLE_METAL
 #include "third_party/skia/include/gpu/ganesh/mtl/GrMtlBackendSurface.h"
 #include "third_party/skia/include/gpu/ganesh/mtl/GrMtlTypes.h"
-#endif
+#endif  // SHELL_ENABLE_METAL
+
+#ifdef SHELL_ENABLE_GL
+#include "flutter/testing/test_gl_surface.h"
+#endif  // SHELL_ENABLE_GL
 
 // TODO(zanderso): https://github.com/flutter/flutter/issues/127701
 // NOLINTBEGIN(bugprone-unchecked-optional-access)
@@ -44,6 +48,10 @@
     : context_(std::move(context)),
       type_(type),
       software_pixfmt_(software_pixfmt)
+#ifdef SHELL_ENABLE_GL
+      ,
+      test_egl_context_(nullptr)
+#endif
 #ifdef SHELL_ENABLE_METAL
       ,
       test_metal_context_(std::make_unique<TestMetalContext>())
@@ -66,6 +74,16 @@
 
 EmbedderTestBackingStoreProducer::~EmbedderTestBackingStoreProducer() = default;
 
+#ifdef SHELL_ENABLE_GL
+void EmbedderTestBackingStoreProducer::SetEGLContext(
+    std::shared_ptr<TestEGLContext> context) {
+  // Ideally this would be set in the constructor, however we can't do that
+  // without introducing different constructors depending on different graphics
+  // APIs, which is a bit ugly.
+  test_egl_context_ = std::move(context);
+}
+#endif
+
 bool EmbedderTestBackingStoreProducer::Create(
     const FlutterBackingStoreConfig* config,
     FlutterBackingStore* renderer_out) {
@@ -79,6 +97,8 @@
       return CreateTexture(config, renderer_out);
     case RenderTargetType::kOpenGLFramebuffer:
       return CreateFramebuffer(config, renderer_out);
+    case RenderTargetType::kOpenGLSurface:
+      return CreateSurface(config, renderer_out);
 #endif
 #ifdef SHELL_ENABLE_METAL
     case RenderTargetType::kMetalTexture:
@@ -130,16 +150,16 @@
     return false;
   }
 
+  auto user_data = new UserData(surface);
+
   backing_store_out->type = kFlutterBackingStoreTypeOpenGL;
-  backing_store_out->user_data = surface.get();
+  backing_store_out->user_data = user_data;
   backing_store_out->open_gl.type = kFlutterOpenGLTargetTypeFramebuffer;
   backing_store_out->open_gl.framebuffer.target = framebuffer_info.fFormat;
   backing_store_out->open_gl.framebuffer.name = framebuffer_info.fFBOID;
-  // The balancing unref is in the destruction callback.
-  surface->ref();
-  backing_store_out->open_gl.framebuffer.user_data = surface.get();
+  backing_store_out->open_gl.framebuffer.user_data = user_data;
   backing_store_out->open_gl.framebuffer.destruction_callback =
-      [](void* user_data) { reinterpret_cast<SkSurface*>(user_data)->unref(); };
+      [](void* user_data) { delete reinterpret_cast<UserData*>(user_data); };
 
   return true;
 #else
@@ -183,17 +203,61 @@
     return false;
   }
 
+  auto user_data = new UserData(surface);
+
   backing_store_out->type = kFlutterBackingStoreTypeOpenGL;
-  backing_store_out->user_data = surface.get();
+  backing_store_out->user_data = user_data;
   backing_store_out->open_gl.type = kFlutterOpenGLTargetTypeTexture;
   backing_store_out->open_gl.texture.target = texture_info.fTarget;
   backing_store_out->open_gl.texture.name = texture_info.fID;
   backing_store_out->open_gl.texture.format = texture_info.fFormat;
-  // The balancing unref is in the destruction callback.
-  surface->ref();
-  backing_store_out->open_gl.texture.user_data = surface.get();
+  backing_store_out->open_gl.texture.user_data = user_data;
   backing_store_out->open_gl.texture.destruction_callback =
-      [](void* user_data) { reinterpret_cast<SkSurface*>(user_data)->unref(); };
+      [](void* user_data) { delete reinterpret_cast<UserData*>(user_data); };
+
+  return true;
+#else
+  return false;
+#endif
+}
+
+bool EmbedderTestBackingStoreProducer::CreateSurface(
+    const FlutterBackingStoreConfig* config,
+    FlutterBackingStore* backing_store_out) {
+#ifdef SHELL_ENABLE_GL
+  FML_CHECK(test_egl_context_);
+  auto surface = std::make_unique<TestGLOnscreenOnlySurface>(
+      test_egl_context_,
+      SkSize::Make(config->size.width, config->size.height).toRound());
+
+  auto make_current = [](void* user_data, bool* invalidate_state) -> bool {
+    *invalidate_state = false;
+    return reinterpret_cast<UserData*>(user_data)->gl_surface->MakeCurrent();
+  };
+
+  auto clear_current = [](void* user_data, bool* invalidate_state) -> bool {
+    *invalidate_state = false;
+    // return
+    // reinterpret_cast<GLUserData*>(user_data)->gl_surface->ClearCurrent();
+    return true;
+  };
+
+  auto destruction_callback = [](void* user_data) {
+    delete reinterpret_cast<UserData*>(user_data);
+  };
+
+  auto sk_surface = surface->GetOnscreenSurface();
+
+  auto user_data = new UserData(sk_surface, nullptr, std::move(surface));
+
+  backing_store_out->type = kFlutterBackingStoreTypeOpenGL;
+  backing_store_out->user_data = user_data;
+  backing_store_out->open_gl.type = kFlutterOpenGLTargetTypeSurface;
+  backing_store_out->open_gl.surface.user_data = user_data;
+  backing_store_out->open_gl.surface.make_current_callback = make_current;
+  backing_store_out->open_gl.surface.clear_current_callback = clear_current;
+  backing_store_out->open_gl.surface.destruction_callback =
+      destruction_callback;
 
   return true;
 #else
@@ -219,16 +283,16 @@
     return false;
   }
 
+  auto user_data = new UserData(surface);
+
   backing_store_out->type = kFlutterBackingStoreTypeSoftware;
-  backing_store_out->user_data = surface.get();
+  backing_store_out->user_data = user_data;
   backing_store_out->software.allocation = pixmap.addr();
   backing_store_out->software.row_bytes = pixmap.rowBytes();
   backing_store_out->software.height = pixmap.height();
-  // The balancing unref is in the destruction callback.
-  surface->ref();
-  backing_store_out->software.user_data = surface.get();
+  backing_store_out->software.user_data = user_data;
   backing_store_out->software.destruction_callback = [](void* user_data) {
-    reinterpret_cast<SkSurface*>(user_data)->unref();
+    delete reinterpret_cast<UserData*>(user_data);
   };
 
   return true;
@@ -256,19 +320,18 @@
     return false;
   }
 
+  auto user_data = new UserData(surface);
+
   backing_store_out->type = kFlutterBackingStoreTypeSoftware2;
-  backing_store_out->user_data = surface.get();
+  backing_store_out->user_data = user_data;
   backing_store_out->software2.struct_size =
       sizeof(FlutterSoftwareBackingStore2);
-  backing_store_out->software2.user_data = surface.get();
   backing_store_out->software2.allocation = pixmap.writable_addr();
   backing_store_out->software2.row_bytes = pixmap.rowBytes();
   backing_store_out->software2.height = pixmap.height();
-  // The balancing unref is in the destruction callback.
-  surface->ref();
-  backing_store_out->software2.user_data = surface.get();
+  backing_store_out->software2.user_data = user_data;
   backing_store_out->software2.destruction_callback = [](void* user_data) {
-    reinterpret_cast<SkSurface*>(user_data)->unref();
+    delete reinterpret_cast<UserData*>(user_data);
   };
   backing_store_out->software2.pixel_format = software_pixfmt_;
 
@@ -374,21 +437,14 @@
 
   // Collect all allocated resources in the destruction_callback.
   {
-    UserData* user_data = new UserData();
-    user_data->image = image;
-    user_data->surface = surface.get();
-
+    auto user_data = new UserData(surface, image);
     backing_store_out->user_data = user_data;
     backing_store_out->vulkan.user_data = user_data;
     backing_store_out->vulkan.destruction_callback = [](void* user_data) {
       UserData* d = reinterpret_cast<UserData*>(user_data);
-      d->surface->unref();
       delete d->image;
       delete d;
     };
-
-    // The balancing unref is in the destruction callback.
-    surface->ref();
   }
 
   return true;
diff --git a/shell/platform/embedder/tests/embedder_test_backingstore_producer.h b/shell/platform/embedder/tests/embedder_test_backingstore_producer.h
index 51b15f7..613b824 100644
--- a/shell/platform/embedder/tests/embedder_test_backingstore_producer.h
+++ b/shell/platform/embedder/tests/embedder_test_backingstore_producer.h
@@ -21,14 +21,36 @@
 #include "flutter/testing/test_vulkan_context.h"  // nogncheck
 #endif
 
+#ifdef SHELL_ENABLE_GL
+#include "flutter/testing/test_gl_surface.h"
+#endif
+
 namespace flutter {
 namespace testing {
 
 class EmbedderTestBackingStoreProducer {
  public:
   struct UserData {
-    SkSurface* surface;
+    UserData() : surface(nullptr), image(nullptr){};
+
+    explicit UserData(sk_sp<SkSurface> surface)
+        : surface(std::move(surface)), image(nullptr){};
+
+    UserData(sk_sp<SkSurface> surface, FlutterVulkanImage* vk_image)
+        : surface(std::move(surface)), image(vk_image){};
+
+    sk_sp<SkSurface> surface;
     FlutterVulkanImage* image;
+#ifdef SHELL_ENABLE_GL
+    UserData(sk_sp<SkSurface> surface,
+             FlutterVulkanImage* vk_image,
+             std::unique_ptr<TestGLOnscreenOnlySurface> gl_surface)
+        : surface(std::move(surface)),
+          image(vk_image),
+          gl_surface(std::move(gl_surface)){};
+
+    std::unique_ptr<TestGLOnscreenOnlySurface> gl_surface;
+#endif
   };
 
   enum class RenderTargetType {
@@ -36,6 +58,7 @@
     kSoftwareBuffer2,
     kOpenGLFramebuffer,
     kOpenGLTexture,
+    kOpenGLSurface,
     kMetalTexture,
     kVulkanImage,
   };
@@ -46,6 +69,10 @@
                                        kFlutterSoftwarePixelFormatNative32);
   ~EmbedderTestBackingStoreProducer();
 
+#ifdef SHELL_ENABLE_GL
+  void SetEGLContext(std::shared_ptr<TestEGLContext> context);
+#endif
+
   bool Create(const FlutterBackingStoreConfig* config,
               FlutterBackingStore* renderer_out);
 
@@ -56,6 +83,9 @@
   bool CreateTexture(const FlutterBackingStoreConfig* config,
                      FlutterBackingStore* renderer_out);
 
+  bool CreateSurface(const FlutterBackingStoreConfig* config,
+                     FlutterBackingStore* renderer_out);
+
   bool CreateSoftware(const FlutterBackingStoreConfig* config,
                       FlutterBackingStore* backing_store_out);
 
@@ -72,6 +102,10 @@
   RenderTargetType type_;
   FlutterSoftwarePixelFormat software_pixfmt_;
 
+#ifdef SHELL_ENABLE_GL
+  std::shared_ptr<TestEGLContext> test_egl_context_;
+#endif
+
 #ifdef SHELL_ENABLE_METAL
   std::unique_ptr<TestMetalContext> test_metal_context_;
 #endif
diff --git a/shell/platform/embedder/tests/embedder_test_compositor_gl.cc b/shell/platform/embedder/tests/embedder_test_compositor_gl.cc
index 50b4da3..29daf8d 100644
--- a/shell/platform/embedder/tests/embedder_test_compositor_gl.cc
+++ b/shell/platform/embedder/tests/embedder_test_compositor_gl.cc
@@ -58,12 +58,29 @@
     SkIPoint canvas_offset = SkIPoint::Make(0, 0);
 
     switch (layer->type) {
-      case kFlutterLayerContentTypeBackingStore:
-        layer_image =
-            reinterpret_cast<SkSurface*>(layer->backing_store->user_data)
-                ->makeImageSnapshot();
+      case kFlutterLayerContentTypeBackingStore: {
+        auto gl_user_data =
+            reinterpret_cast<EmbedderTestBackingStoreProducer::UserData*>(
+                layer->backing_store->user_data);
 
+        if (gl_user_data->gl_surface != nullptr) {
+          // This backing store is a OpenGL Surface.
+          // We need to make it current so we can snapshot it.
+
+          gl_user_data->gl_surface->MakeCurrent();
+
+          // GetRasterSurfaceSnapshot() does two
+          // gl_surface->makeImageSnapshot()'s. Doing a single
+          // ->makeImageSnapshot() will not work.
+          layer_image = gl_user_data->gl_surface->GetRasterSurfaceSnapshot();
+        } else {
+          layer_image = gl_user_data->surface->makeImageSnapshot();
+        }
+
+        // We don't clear the current surface here because we need the
+        // EGL context to be current for surface->makeImageSnapshot() below.
         break;
+      }
       case kFlutterLayerContentTypePlatformView:
         layer_image =
             platform_view_renderer_callback_
diff --git a/shell/platform/embedder/tests/embedder_test_compositor_software.cc b/shell/platform/embedder/tests/embedder_test_compositor_software.cc
index 0a9143f..4693820 100644
--- a/shell/platform/embedder/tests/embedder_test_compositor_software.cc
+++ b/shell/platform/embedder/tests/embedder_test_compositor_software.cc
@@ -48,8 +48,9 @@
     switch (layer->type) {
       case kFlutterLayerContentTypeBackingStore:
         layer_image =
-            reinterpret_cast<SkSurface*>(layer->backing_store->user_data)
-                ->makeImageSnapshot();
+            reinterpret_cast<EmbedderTestBackingStoreProducer::UserData*>(
+                layer->backing_store->user_data)
+                ->surface->makeImageSnapshot();
 
         break;
       case kFlutterLayerContentTypePlatformView:
diff --git a/shell/platform/embedder/tests/embedder_test_context.h b/shell/platform/embedder/tests/embedder_test_context.h
index 6f1a156..b99d6b3 100644
--- a/shell/platform/embedder/tests/embedder_test_context.h
+++ b/shell/platform/embedder/tests/embedder_test_context.h
@@ -122,6 +122,10 @@
   fml::RefPtr<TestVulkanContext> vulkan_context_ = nullptr;
 #endif
 
+#ifdef SHELL_ENABLE_GL
+  std::shared_ptr<TestEGLContext> egl_context_ = nullptr;
+#endif
+
   std::string assets_path_;
   ELFAOTSymbols aot_symbols_;
   std::unique_ptr<fml::Mapping> vm_snapshot_data_;
diff --git a/shell/platform/embedder/tests/embedder_test_context_gl.cc b/shell/platform/embedder/tests/embedder_test_context_gl.cc
index ed3dabc..ac916ea 100644
--- a/shell/platform/embedder/tests/embedder_test_context_gl.cc
+++ b/shell/platform/embedder/tests/embedder_test_context_gl.cc
@@ -20,7 +20,9 @@
 namespace testing {
 
 EmbedderTestContextGL::EmbedderTestContextGL(std::string assets_path)
-    : EmbedderTestContext(std::move(assets_path)) {}
+    : EmbedderTestContext(std::move(assets_path)) {
+  egl_context_ = std::make_shared<TestEGLContext>();
+}
 
 EmbedderTestContextGL::~EmbedderTestContextGL() {
   SetGLGetFBOCallback(nullptr);
@@ -28,7 +30,7 @@
 
 void EmbedderTestContextGL::SetupSurface(SkISize surface_size) {
   FML_CHECK(!gl_surface_);
-  gl_surface_ = std::make_unique<TestGLSurface>(surface_size);
+  gl_surface_ = std::make_unique<TestGLSurface>(egl_context_, surface_size);
 }
 
 bool EmbedderTestContextGL::GLMakeCurrent() {
diff --git a/shell/platform/embedder/tests/embedder_test_context_gl.h b/shell/platform/embedder/tests/embedder_test_context_gl.h
index f23f98f..a7c8808 100644
--- a/shell/platform/embedder/tests/embedder_test_context_gl.h
+++ b/shell/platform/embedder/tests/embedder_test_context_gl.h
@@ -67,6 +67,8 @@
  protected:
   virtual void SetupCompositor() override;
 
+  void SetupCompositorUsingGLSurfaces();
+
  private:
   // This allows the builder to access the hooks.
   friend class EmbedderConfigBuilder;
diff --git a/shell/platform/embedder/tests/embedder_unittests.cc b/shell/platform/embedder/tests/embedder_unittests.cc
index 2b03e51..22cdb54 100644
--- a/shell/platform/embedder/tests/embedder_unittests.cc
+++ b/shell/platform/embedder/tests/embedder_unittests.cc
@@ -2735,8 +2735,9 @@
         ASSERT_EQ(layers[0]->backing_store->type,
                   kFlutterBackingStoreTypeSoftware2);
         matches = SurfacePixelDataMatchesBytes(
-            static_cast<SkSurface*>(
-                layers[0]->backing_store->software2.user_data),
+            reinterpret_cast<EmbedderTestBackingStoreProducer::UserData*>(
+                layers[0]->backing_store->software2.user_data)
+                ->surface.get(),
             bytes);
         latch.Signal();
       });
diff --git a/testing/test_gl_surface.cc b/testing/test_gl_surface.cc
index c703646..25083c1 100644
--- a/testing/test_gl_surface.cc
+++ b/testing/test_gl_surface.cc
@@ -130,15 +130,14 @@
       display_config);
 }
 
-TestGLSurface::TestGLSurface(SkISize surface_size)
-    : surface_size_(surface_size) {
-  display_ = CreateSwangleDisplay();
-  FML_CHECK(display_ != EGL_NO_DISPLAY);
+TestEGLContext::TestEGLContext() {
+  display = CreateSwangleDisplay();
+  FML_CHECK(display != EGL_NO_DISPLAY);
 
-  auto result = ::eglInitialize(display_, nullptr, nullptr);
+  auto result = ::eglInitialize(display, nullptr, nullptr);
   FML_CHECK(result == EGL_TRUE) << GetEGLError();
 
-  EGLConfig config = {0};
+  config = {0};
 
   EGLint num_config = 0;
   const EGLint attribute_list[] = {EGL_RED_SIZE,
@@ -157,90 +156,79 @@
                                    EGL_OPENGL_ES2_BIT,
                                    EGL_NONE};
 
-  result = ::eglChooseConfig(display_, attribute_list, &config, 1, &num_config);
+  result = ::eglChooseConfig(display, attribute_list, &config, 1, &num_config);
   FML_CHECK(result == EGL_TRUE) << GetEGLError();
   FML_CHECK(num_config == 1) << GetEGLError();
 
   {
-    const EGLint onscreen_surface_attributes[] = {
-        EGL_WIDTH,  surface_size_.width(),   //
-        EGL_HEIGHT, surface_size_.height(),  //
-        EGL_NONE,
-    };
-
-    onscreen_surface_ = ::eglCreatePbufferSurface(
-        display_,                    // display connection
-        config,                      // config
-        onscreen_surface_attributes  // surface attributes
-    );
-    FML_CHECK(onscreen_surface_ != EGL_NO_SURFACE) << GetEGLError();
-  }
-
-  {
-    const EGLint offscreen_surface_attributes[] = {
-        EGL_WIDTH,  1,  //
-        EGL_HEIGHT, 1,  //
-        EGL_NONE,
-    };
-    offscreen_surface_ = ::eglCreatePbufferSurface(
-        display_,                     // display connection
-        config,                       // config
-        offscreen_surface_attributes  // surface attributes
-    );
-    FML_CHECK(offscreen_surface_ != EGL_NO_SURFACE) << GetEGLError();
-  }
-
-  {
     const EGLint context_attributes[] = {
         EGL_CONTEXT_CLIENT_VERSION,  //
         2,                           //
         EGL_NONE                     //
     };
 
-    onscreen_context_ =
-        ::eglCreateContext(display_,           // display connection
+    onscreen_context =
+        ::eglCreateContext(display,            // display connection
                            config,             // config
                            EGL_NO_CONTEXT,     // sharegroup
                            context_attributes  // context attributes
         );
-    FML_CHECK(onscreen_context_ != EGL_NO_CONTEXT) << GetEGLError();
+    FML_CHECK(onscreen_context != EGL_NO_CONTEXT) << GetEGLError();
 
-    offscreen_context_ =
-        ::eglCreateContext(display_,           // display connection
+    offscreen_context =
+        ::eglCreateContext(display,            // display connection
                            config,             // config
-                           onscreen_context_,  // sharegroup
+                           onscreen_context,   // sharegroup
                            context_attributes  // context attributes
         );
-    FML_CHECK(offscreen_context_ != EGL_NO_CONTEXT) << GetEGLError();
+    FML_CHECK(offscreen_context != EGL_NO_CONTEXT) << GetEGLError();
   }
 }
 
-TestGLSurface::~TestGLSurface() {
-  context_ = nullptr;
-
-  auto result = ::eglDestroyContext(display_, onscreen_context_);
+TestEGLContext::~TestEGLContext() {
+  auto result = ::eglDestroyContext(display, onscreen_context);
   FML_CHECK(result == EGL_TRUE) << GetEGLError();
 
-  result = ::eglDestroyContext(display_, offscreen_context_);
+  result = ::eglDestroyContext(display, offscreen_context);
   FML_CHECK(result == EGL_TRUE) << GetEGLError();
 
-  result = ::eglDestroySurface(display_, onscreen_surface_);
-  FML_CHECK(result == EGL_TRUE) << GetEGLError();
-
-  result = ::eglDestroySurface(display_, offscreen_surface_);
-  FML_CHECK(result == EGL_TRUE) << GetEGLError();
-
-  result = ::eglTerminate(display_);
+  result = ::eglTerminate(display);
   FML_CHECK(result == EGL_TRUE);
 }
 
-const SkISize& TestGLSurface::GetSurfaceSize() const {
+TestGLOnscreenOnlySurface::TestGLOnscreenOnlySurface(
+    std::shared_ptr<TestEGLContext> context,
+    SkISize size)
+    : surface_size_(size), egl_context_(std::move(context)) {
+  const EGLint attributes[] = {
+      EGL_WIDTH,  size.width(),   //
+      EGL_HEIGHT, size.height(),  //
+      EGL_NONE,
+  };
+
+  onscreen_surface_ =
+      ::eglCreatePbufferSurface(egl_context_->display,  // display connection
+                                egl_context_->config,   // config
+                                attributes              // surface attributes
+      );
+  FML_CHECK(onscreen_surface_ != EGL_NO_SURFACE) << GetEGLError();
+}
+
+TestGLOnscreenOnlySurface::~TestGLOnscreenOnlySurface() {
+  skia_context_ = nullptr;
+
+  auto result = ::eglDestroySurface(egl_context_->display, onscreen_surface_);
+  FML_CHECK(result == EGL_TRUE) << GetEGLError();
+}
+
+const SkISize& TestGLOnscreenOnlySurface::GetSurfaceSize() const {
   return surface_size_;
 }
 
-bool TestGLSurface::MakeCurrent() {
-  auto result = ::eglMakeCurrent(display_, onscreen_surface_, onscreen_surface_,
-                                 onscreen_context_);
+bool TestGLOnscreenOnlySurface::MakeCurrent() {
+  auto result =
+      ::eglMakeCurrent(egl_context_->display, onscreen_surface_,
+                       onscreen_surface_, egl_context_->onscreen_context);
 
   if (result == EGL_FALSE) {
     FML_LOG(ERROR) << "Could not make the context current. " << GetEGLError();
@@ -249,9 +237,9 @@
   return result == EGL_TRUE;
 }
 
-bool TestGLSurface::ClearCurrent() {
-  auto result = ::eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE,
-                                 EGL_NO_CONTEXT);
+bool TestGLOnscreenOnlySurface::ClearCurrent() {
+  auto result = ::eglMakeCurrent(egl_context_->display, EGL_NO_SURFACE,
+                                 EGL_NO_SURFACE, EGL_NO_CONTEXT);
 
   if (result == EGL_FALSE) {
     FML_LOG(ERROR) << "Could not clear the current context. " << GetEGLError();
@@ -260,8 +248,8 @@
   return result == EGL_TRUE;
 }
 
-bool TestGLSurface::Present() {
-  auto result = ::eglSwapBuffers(display_, onscreen_surface_);
+bool TestGLOnscreenOnlySurface::Present() {
+  auto result = ::eglSwapBuffers(egl_context_->display, onscreen_surface_);
 
   if (result == EGL_FALSE) {
     FML_LOG(ERROR) << "Could not swap buffers. " << GetEGLError();
@@ -270,23 +258,12 @@
   return result == EGL_TRUE;
 }
 
-uint32_t TestGLSurface::GetFramebuffer(uint32_t width, uint32_t height) const {
+uint32_t TestGLOnscreenOnlySurface::GetFramebuffer(uint32_t width,
+                                                   uint32_t height) const {
   return GetWindowFBOId();
 }
 
-bool TestGLSurface::MakeResourceCurrent() {
-  auto result = ::eglMakeCurrent(display_, offscreen_surface_,
-                                 offscreen_surface_, offscreen_context_);
-
-  if (result == EGL_FALSE) {
-    FML_LOG(ERROR) << "Could not make the resource context current. "
-                   << GetEGLError();
-  }
-
-  return result == EGL_TRUE;
-}
-
-void* TestGLSurface::GetProcAddress(const char* name) const {
+void* TestGLOnscreenOnlySurface::GetProcAddress(const char* name) const {
   if (name == nullptr) {
     return nullptr;
   }
@@ -297,15 +274,15 @@
   return reinterpret_cast<void*>(symbol);
 }
 
-sk_sp<GrDirectContext> TestGLSurface::GetGrContext() {
-  if (context_) {
-    return context_;
+sk_sp<GrDirectContext> TestGLOnscreenOnlySurface::GetGrContext() {
+  if (skia_context_) {
+    return skia_context_;
   }
 
   return CreateGrContext();
 }
 
-sk_sp<GrDirectContext> TestGLSurface::CreateGrContext() {
+sk_sp<GrDirectContext> TestGLOnscreenOnlySurface::CreateGrContext() {
   if (!MakeCurrent()) {
     return nullptr;
   }
@@ -325,7 +302,8 @@
 
   GrGLGetProc get_proc = [](void* context, const char name[]) -> GrGLFuncPtr {
     return reinterpret_cast<GrGLFuncPtr>(
-        reinterpret_cast<TestGLSurface*>(context)->GetProcAddress(name));
+        reinterpret_cast<TestGLOnscreenOnlySurface*>(context)->GetProcAddress(
+            name));
   };
 
   std::string version(c_version);
@@ -337,11 +315,11 @@
     return nullptr;
   }
 
-  context_ = GrDirectContexts::MakeGL(interface);
-  return context_;
+  skia_context_ = GrDirectContexts::MakeGL(interface);
+  return skia_context_;
 }
 
-sk_sp<SkSurface> TestGLSurface::GetOnscreenSurface() {
+sk_sp<SkSurface> TestGLOnscreenOnlySurface::GetOnscreenSurface() {
   FML_CHECK(::eglGetCurrentContext() != EGL_NO_CONTEXT);
 
   GrGLFramebufferInfo framebuffer_info = {};
@@ -384,7 +362,7 @@
   return surface;
 }
 
-sk_sp<SkImage> TestGLSurface::GetRasterSurfaceSnapshot() {
+sk_sp<SkImage> TestGLOnscreenOnlySurface::GetRasterSurfaceSnapshot() {
   auto surface = GetOnscreenSurface();
 
   if (!surface) {
@@ -412,9 +390,48 @@
   return host_snapshot;
 }
 
-uint32_t TestGLSurface::GetWindowFBOId() const {
+uint32_t TestGLOnscreenOnlySurface::GetWindowFBOId() const {
   return 0u;
 }
 
+TestGLSurface::TestGLSurface(SkISize surface_size)
+    : TestGLSurface(std::make_shared<TestEGLContext>(), surface_size) {}
+
+TestGLSurface::TestGLSurface(std::shared_ptr<TestEGLContext> egl_context,
+                             SkISize surface_size)
+    : TestGLOnscreenOnlySurface(std::move(egl_context), surface_size) {
+  {
+    const EGLint offscreen_surface_attributes[] = {
+        EGL_WIDTH,  1,  //
+        EGL_HEIGHT, 1,  //
+        EGL_NONE,
+    };
+    offscreen_surface_ = ::eglCreatePbufferSurface(
+        egl_context_->display,        // display connection
+        egl_context_->config,         // config
+        offscreen_surface_attributes  // surface attributes
+    );
+    FML_CHECK(offscreen_surface_ != EGL_NO_SURFACE) << GetEGLError();
+  }
+}
+
+TestGLSurface::~TestGLSurface() {
+  auto result = ::eglDestroySurface(egl_context_->display, offscreen_surface_);
+  FML_CHECK(result == EGL_TRUE) << GetEGLError();
+}
+
+bool TestGLSurface::MakeResourceCurrent() {
+  auto result =
+      ::eglMakeCurrent(egl_context_->display, offscreen_surface_,
+                       offscreen_surface_, egl_context_->offscreen_context);
+
+  if (result == EGL_FALSE) {
+    FML_LOG(ERROR) << "Could not make the resource context current. "
+                   << GetEGLError();
+  }
+
+  return result == EGL_TRUE;
+}
+
 }  // namespace testing
 }  // namespace flutter
diff --git a/testing/test_gl_surface.h b/testing/test_gl_surface.h
index 32bd732..654d2cd 100644
--- a/testing/test_gl_surface.h
+++ b/testing/test_gl_surface.h
@@ -15,11 +15,33 @@
 namespace flutter {
 namespace testing {
 
-class TestGLSurface {
- public:
-  explicit TestGLSurface(SkISize surface_size);
+struct TestEGLContext {
+  explicit TestEGLContext();
 
-  ~TestGLSurface();
+  ~TestEGLContext();
+
+  using EGLDisplay = void*;
+  using EGLContext = void*;
+  using EGLConfig = void*;
+
+  EGLDisplay display;
+  EGLContext onscreen_context;
+  EGLContext offscreen_context;
+
+  // EGLConfig is technically a property of the surfaces, no the context,
+  // but it's not that well separated in EGL (e.g. when
+  // EGL_KHR_no_config_context is not supported), so we just store it here.
+  EGLConfig config;
+};
+
+class TestGLOnscreenOnlySurface {
+ public:
+  explicit TestGLOnscreenOnlySurface(SkISize surface_size);
+
+  explicit TestGLOnscreenOnlySurface(std::shared_ptr<TestEGLContext> context,
+                                     SkISize size);
+
+  ~TestGLOnscreenOnlySurface();
 
   const SkISize& GetSurfaceSize() const;
 
@@ -31,8 +53,6 @@
 
   uint32_t GetFramebuffer(uint32_t width, uint32_t height) const;
 
-  bool MakeResourceCurrent();
-
   void* GetProcAddress(const char* name) const;
 
   sk_sp<SkSurface> GetOnscreenSurface();
@@ -45,22 +65,33 @@
 
   uint32_t GetWindowFBOId() const;
 
- private:
-  // Importing the EGL.h pulls in platform headers which are problematic
-  // (especially X11 which #defineds types like Bool). Any TUs importing
-  // this header then become susceptible to failures because of platform
-  // specific craziness. Don't expose EGL internals via this header.
-  using EGLDisplay = void*;
-  using EGLContext = void*;
+ protected:
   using EGLSurface = void*;
 
   const SkISize surface_size_;
-  EGLDisplay display_;
-  EGLContext onscreen_context_;
-  EGLContext offscreen_context_;
+  std::shared_ptr<TestEGLContext> egl_context_;
   EGLSurface onscreen_surface_;
+
+  sk_sp<GrDirectContext> skia_context_;
+
+  FML_DISALLOW_COPY_AND_ASSIGN(TestGLOnscreenOnlySurface);
+};
+
+class TestGLSurface : public TestGLOnscreenOnlySurface {
+ public:
+  explicit TestGLSurface(SkISize surface_size);
+
+  explicit TestGLSurface(std::shared_ptr<TestEGLContext> egl_context,
+                         SkISize surface_size);
+
+  ~TestGLSurface();
+
+  bool MakeResourceCurrent();
+
+ private:
+  using EGLSurface = void*;
+
   EGLSurface offscreen_surface_;
-  sk_sp<GrDirectContext> context_;
 
   FML_DISALLOW_COPY_AND_ASSIGN(TestGLSurface);
 };