[impeller] Use the GLES3 shaders in the embedder if supported (#180072)

<!--
Thanks for filing a pull request!
Reviewers are typically assigned within a week of filing a request.
To learn more about code review, see our documentation on Tree Hygiene:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
-->

Adds the GLES3 shaders to the embedder (used by Linux and custom
embedders) if they are supported.

Fixes #179185

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

---------

Co-authored-by: Jason Simmons <jsimmons@google.com>
Co-authored-by: Jason Simmons <jason-simmons@users.noreply.github.com>
diff --git a/docs/engine/testing/Testing-the-engine.md b/docs/engine/testing/Testing-the-engine.md
index 74ac146..0cfa214 100644
--- a/docs/engine/testing/Testing-the-engine.md
+++ b/docs/engine/testing/Testing-the-engine.md
@@ -34,6 +34,17 @@
 testing/run_tests.py --type=engine --variant=host_debug_unopt_arm64
 ```
 
+Some tests depend on the underlying Graphics Driver implementation which can result
+in test flakyness. Test reliability on Linux for OpenGLES can be improved by selecting the
+[Software OpenGLES renderer](https://docs.mesa3d.org/drivers/llvmpipe.html)
+with [libglvnd](https://gitlab.freedesktop.org/glvnd/libglvnd). For example:
+
+```sh
+__EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/50_mesa.json
+testing/run_tests.py
+```
+Check your distribution's documentation for more details.
+
 Behind the scenes, those tests in the same directory are built together as a
 testonly executable when you build the engine variant. The `run_tests.py` script
 executes them one by one.
diff --git a/engine/src/flutter/impeller/display_list/aiks_dl_runtime_effect_unittests.cc b/engine/src/flutter/impeller/display_list/aiks_dl_runtime_effect_unittests.cc
index c8a9e21..73a4cd5 100644
--- a/engine/src/flutter/impeller/display_list/aiks_dl_runtime_effect_unittests.cc
+++ b/engine/src/flutter/impeller/display_list/aiks_dl_runtime_effect_unittests.cc
@@ -35,8 +35,7 @@
     return runtime_stages_result.status();
   }
   std::shared_ptr<RuntimeStage> runtime_stage =
-      runtime_stages_result
-          .value()[PlaygroundBackendToRuntimeStageBackend(test->GetBackend())];
+      runtime_stages_result.value()[test->GetRuntimeStageBackend()];
   if (!runtime_stage) {
     return absl::InternalError("Runtime stage not found for backend.");
   }
@@ -105,8 +104,7 @@
       OpenAssetAsRuntimeStage("runtime_stage_filter_example.frag.iplr");
   ABSL_ASSERT_OK(runtime_stages_result);
   std::shared_ptr<RuntimeStage> runtime_stage =
-      runtime_stages_result
-          .value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
+      runtime_stages_result.value()[GetRuntimeStageBackend()];
   ASSERT_TRUE(runtime_stage);
   ASSERT_TRUE(runtime_stage->IsDirty());
 
@@ -179,8 +177,7 @@
       OpenAssetAsRuntimeStage("runtime_stage_filter_warp.frag.iplr");
   ABSL_ASSERT_OK(runtime_stages_result);
   std::shared_ptr<RuntimeStage> runtime_stage =
-      runtime_stages_result
-          .value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
+      runtime_stages_result.value()[GetRuntimeStageBackend()];
   ASSERT_TRUE(runtime_stage);
   ASSERT_TRUE(runtime_stage->IsDirty());
 
@@ -216,8 +213,7 @@
       OpenAssetAsRuntimeStage("runtime_stage_filter_warp.frag.iplr");
   ABSL_ASSERT_OK(runtime_stages_result);
   std::shared_ptr<RuntimeStage> runtime_stage =
-      runtime_stages_result
-          .value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
+      runtime_stages_result.value()[GetRuntimeStageBackend()];
   ASSERT_TRUE(runtime_stage);
   ASSERT_TRUE(runtime_stage->IsDirty());
   Scalar xoffset = 50;
@@ -304,8 +300,7 @@
       OpenAssetAsRuntimeStage("runtime_stage_filter_circle.frag.iplr");
   ABSL_ASSERT_OK(runtime_stages_result);
   std::shared_ptr<RuntimeStage> runtime_stage =
-      runtime_stages_result
-          .value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
+      runtime_stages_result.value()[GetRuntimeStageBackend()];
   ASSERT_TRUE(runtime_stage);
   ASSERT_TRUE(runtime_stage->IsDirty());
   Scalar sigma = 20.0;
@@ -369,8 +364,7 @@
       OpenAssetAsRuntimeStage("runtime_stage_filter_circle.frag.iplr");
   ABSL_ASSERT_OK(runtime_stages_result);
   std::shared_ptr<RuntimeStage> runtime_stage =
-      runtime_stages_result
-          .value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
+      runtime_stages_result.value()[GetRuntimeStageBackend()];
   ASSERT_TRUE(runtime_stage);
   ASSERT_TRUE(runtime_stage->IsDirty());
   Scalar sigma = 5.0;
@@ -433,8 +427,7 @@
       OpenAssetAsRuntimeStage("runtime_stage_filter_circle.frag.iplr");
   ABSL_ASSERT_OK(runtime_stages_result);
   std::shared_ptr<RuntimeStage> runtime_stage =
-      runtime_stages_result
-          .value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
+      runtime_stages_result.value()[GetRuntimeStageBackend()];
   ASSERT_TRUE(runtime_stage);
   ASSERT_TRUE(runtime_stage->IsDirty());
   Scalar sigma = 5.0;
@@ -516,8 +509,7 @@
       OpenAssetAsRuntimeStage("runtime_stage_border.frag.iplr");
   ABSL_ASSERT_OK(runtime_stages_result);
   std::shared_ptr<RuntimeStage> runtime_stage =
-      runtime_stages_result
-          .value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
+      runtime_stages_result.value()[GetRuntimeStageBackend()];
   ASSERT_TRUE(runtime_stage);
   ASSERT_TRUE(runtime_stage->IsDirty());
 
@@ -577,8 +569,7 @@
   auto runtime_stages_result = OpenAssetAsRuntimeStage("gradient.frag.iplr");
   ABSL_ASSERT_OK(runtime_stages_result);
   std::shared_ptr<RuntimeStage> runtime_stage =
-      runtime_stages_result
-          .value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
+      runtime_stages_result.value()[GetRuntimeStageBackend()];
   ASSERT_TRUE(runtime_stage);
   ASSERT_TRUE(runtime_stage->IsDirty());
 
diff --git a/engine/src/flutter/impeller/display_list/aiks_dl_vertices_unittests.cc b/engine/src/flutter/impeller/display_list/aiks_dl_vertices_unittests.cc
index 6f31b96..55d8473 100644
--- a/engine/src/flutter/impeller/display_list/aiks_dl_vertices_unittests.cc
+++ b/engine/src/flutter/impeller/display_list/aiks_dl_vertices_unittests.cc
@@ -482,8 +482,7 @@
       OpenAssetAsRuntimeStage("runtime_stage_simple.frag.iplr");
   ABSL_ASSERT_OK(runtime_stages_result);
   std::shared_ptr<RuntimeStage> runtime_stage =
-      runtime_stages_result
-          .value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
+      runtime_stages_result.value()[GetRuntimeStageBackend()];
   ASSERT_TRUE(runtime_stage);
 
   auto runtime_effect = DlRuntimeEffectImpeller::Make(runtime_stage);
@@ -532,8 +531,7 @@
       OpenAssetAsRuntimeStage("runtime_stage_position.frag.iplr");
   ABSL_ASSERT_OK(runtime_stages_result);
   std::shared_ptr<RuntimeStage> runtime_stage =
-      runtime_stages_result
-          .value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
+      runtime_stages_result.value()[GetRuntimeStageBackend()];
   ASSERT_TRUE(runtime_stage);
 
   auto runtime_effect = DlRuntimeEffectImpeller::Make(runtime_stage);
diff --git a/engine/src/flutter/impeller/display_list/canvas_unittests.cc b/engine/src/flutter/impeller/display_list/canvas_unittests.cc
index 7ca94c4..6bcac03 100644
--- a/engine/src/flutter/impeller/display_list/canvas_unittests.cc
+++ b/engine/src/flutter/impeller/display_list/canvas_unittests.cc
@@ -332,8 +332,7 @@
       OpenAssetAsRuntimeStage("runtime_stage_simple.frag.iplr");
   ABSL_ASSERT_OK(runtime_stages_result);
   std::shared_ptr<RuntimeStage> runtime_stage =
-      runtime_stages_result
-          .value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
+      runtime_stages_result.value()[GetRuntimeStageBackend()];
   ASSERT_TRUE(runtime_stage);
 
   auto runtime_effect = flutter::DlRuntimeEffectImpeller::Make(runtime_stage);
diff --git a/engine/src/flutter/impeller/entity/entity_unittests.cc b/engine/src/flutter/impeller/entity/entity_unittests.cc
index aae5107..69ce491 100644
--- a/engine/src/flutter/impeller/entity/entity_unittests.cc
+++ b/engine/src/flutter/impeller/entity/entity_unittests.cc
@@ -1759,8 +1759,7 @@
       OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr");
   ABSL_ASSERT_OK(runtime_stages_result);
   std::shared_ptr<RuntimeStage> runtime_stage =
-      runtime_stages_result
-          .value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
+      runtime_stages_result.value()[GetRuntimeStageBackend()];
   ASSERT_TRUE(runtime_stage);
   ASSERT_TRUE(runtime_stage->IsDirty());
 
@@ -1816,9 +1815,7 @@
     auto runtime_stages_result =
         OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr");
     ABSL_ASSERT_OK(runtime_stages_result);
-    runtime_stage =
-        runtime_stages_result
-            .value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
+    runtime_stage = runtime_stages_result.value()[GetRuntimeStageBackend()];
 
     ASSERT_TRUE(runtime_stage->IsDirty());
     expect_dirty = true;
@@ -1831,9 +1828,7 @@
   auto runtime_stages_result =
       OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr");
   ABSL_ASSERT_OK(runtime_stages_result);
-  auto runtime_stage =
-      runtime_stages_result
-          .value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
+  auto runtime_stage = runtime_stages_result.value()[GetRuntimeStageBackend()];
   ASSERT_TRUE(runtime_stage);
   ASSERT_TRUE(runtime_stage->IsDirty());
 
@@ -1881,8 +1876,7 @@
       OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr");
   ABSL_ASSERT_OK(runtime_stages_result);
   std::shared_ptr<RuntimeStage> runtime_stage =
-      runtime_stages_result
-          .value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
+      runtime_stages_result.value()[GetRuntimeStageBackend()];
   ASSERT_TRUE(runtime_stage);
   ASSERT_TRUE(runtime_stage->IsDirty());
 
@@ -1900,9 +1894,7 @@
   auto runtime_stages_result =
       OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr");
   ABSL_ASSERT_OK(runtime_stages_result);
-  auto runtime_stage =
-      runtime_stages_result
-          .value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
+  auto runtime_stage = runtime_stages_result.value()[GetRuntimeStageBackend()];
   ASSERT_TRUE(runtime_stage);
   ASSERT_TRUE(runtime_stage->IsDirty());
 
diff --git a/engine/src/flutter/impeller/golden_tests/golden_playground_test.h b/engine/src/flutter/impeller/golden_tests/golden_playground_test.h
index 9158eb6..cc1def8 100644
--- a/engine/src/flutter/impeller/golden_tests/golden_playground_test.h
+++ b/engine/src/flutter/impeller/golden_tests/golden_playground_test.h
@@ -11,7 +11,7 @@
 #include "flutter/display_list/image/dl_image.h"
 #include "flutter/impeller/display_list/aiks_context.h"
 #include "flutter/impeller/golden_tests/screenshot.h"
-#include "flutter/impeller/renderer/render_target.h"
+#include "flutter/impeller/runtime_stage/runtime_stage.h"
 #include "flutter/testing/testing.h"
 #include "impeller/playground/playground.h"
 #include "impeller/typographer/typographer_context.h"
@@ -90,6 +90,8 @@
   /// Returns true if `OpenPlaygroundHere` will actually render anything.
   bool WillRenderSomething() const { return true; }
 
+  RuntimeStageBackend GetRuntimeStageBackend() const;
+
  protected:
   void SetWindowSize(ISize size);
 
diff --git a/engine/src/flutter/impeller/golden_tests/golden_playground_test_mac.cc b/engine/src/flutter/impeller/golden_tests/golden_playground_test_mac.cc
index 72e6637..d139104 100644
--- a/engine/src/flutter/impeller/golden_tests/golden_playground_test_mac.cc
+++ b/engine/src/flutter/impeller/golden_tests/golden_playground_test_mac.cc
@@ -344,4 +344,8 @@
       renderer, DisplayListToTexture(list, physical_window_size, renderer));
 }
 
+RuntimeStageBackend GoldenPlaygroundTest::GetRuntimeStageBackend() const {
+  return pimpl_->screenshotter->GetPlayground().GetRuntimeStageBackend();
+}
+
 }  // namespace impeller
diff --git a/engine/src/flutter/impeller/golden_tests/golden_playground_test_stub.cc b/engine/src/flutter/impeller/golden_tests/golden_playground_test_stub.cc
index 69e0017..e1ad152 100644
--- a/engine/src/flutter/impeller/golden_tests/golden_playground_test_stub.cc
+++ b/engine/src/flutter/impeller/golden_tests/golden_playground_test_stub.cc
@@ -82,4 +82,8 @@
   return nullptr;
 }
 
+RuntimeStageBackend GoldenPlaygroundTest::GetRuntimeStageBackend() const {
+  FML_UNREACHABLE();
+}
+
 }  // namespace impeller
diff --git a/engine/src/flutter/impeller/playground/backend/gles/playground_impl_gles.cc b/engine/src/flutter/impeller/playground/backend/gles/playground_impl_gles.cc
index a1a075b..32b00b9 100644
--- a/engine/src/flutter/impeller/playground/backend/gles/playground_impl_gles.cc
+++ b/engine/src/flutter/impeller/playground/backend/gles/playground_impl_gles.cc
@@ -17,9 +17,15 @@
 #include "impeller/entity/gles/entity_shaders_gles.h"
 #include "impeller/entity/gles/framebuffer_blend_shaders_gles.h"
 #include "impeller/entity/gles/modern_shaders_gles.h"
+#include "impeller/entity/gles3/entity_shaders_gles.h"
+#include "impeller/entity/gles3/framebuffer_blend_shaders_gles.h"
+#include "impeller/entity/gles3/modern_shaders_gles.h"
 #include "impeller/fixtures/gles/fixtures_shaders_gles.h"
 #include "impeller/fixtures/gles/modern_fixtures_shaders_gles.h"
+#include "impeller/fixtures/gles3/fixtures_shaders_gles.h"
+#include "impeller/fixtures/gles3/modern_fixtures_shaders_gles.h"
 #include "impeller/playground/imgui/gles/imgui_shaders_gles.h"
+#include "impeller/playground/imgui/gles3/imgui_shaders_gles.h"
 #include "impeller/renderer/backend/gles/context_gles.h"
 #include "impeller/renderer/backend/gles/surface_gles.h"
 
@@ -111,7 +117,29 @@
 PlaygroundImplGLES::~PlaygroundImplGLES() = default;
 
 static std::vector<std::shared_ptr<fml::Mapping>>
-ShaderLibraryMappingsForPlayground() {
+ShaderLibraryMappingsForPlayground(bool is_gles3) {
+  if (is_gles3) {
+    return {
+        std::make_shared<fml::NonOwnedMapping>(
+            impeller_entity_shaders_gles3_data,
+            impeller_entity_shaders_gles3_length),
+        std::make_shared<fml::NonOwnedMapping>(
+            impeller_modern_shaders_gles3_data,
+            impeller_modern_shaders_gles3_length),
+        std::make_shared<fml::NonOwnedMapping>(
+            impeller_framebuffer_blend_shaders_gles3_data,
+            impeller_framebuffer_blend_shaders_gles3_length),
+        std::make_shared<fml::NonOwnedMapping>(
+            impeller_fixtures_shaders_gles3_data,
+            impeller_fixtures_shaders_gles3_length),
+        std::make_shared<fml::NonOwnedMapping>(
+            impeller_modern_fixtures_shaders_gles3_data,
+            impeller_modern_fixtures_shaders_gles3_length),
+        std::make_shared<fml::NonOwnedMapping>(
+            impeller_imgui_shaders_gles3_data,
+            impeller_imgui_shaders_gles3_length),
+    };
+  }
   return {
       std::make_shared<fml::NonOwnedMapping>(
           impeller_entity_shaders_gles_data,
@@ -160,9 +188,10 @@
     gl->Enable(GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR);
 #endif
   }
+  bool is_gles3 = gl->GetDescription()->GetGlVersion().IsAtLeast(Version(3));
   auto context =
       ContextGLES::Create(switches_.flags, std::move(gl),
-                          ShaderLibraryMappingsForPlayground(), true);
+                          ShaderLibraryMappingsForPlayground(is_gles3), true);
   if (!context) {
     FML_LOG(ERROR) << "Could not create context.";
     return nullptr;
@@ -227,4 +256,16 @@
       "PlaygroundImplGLES doesn't support setting the capabilities.");
 }
 
+RuntimeStageBackend PlaygroundImplGLES::GetRuntimeStageBackend() const {
+  const auto gl =
+      std::make_unique<ProcTableGLES>(CreateGLProcAddressResolver());
+  if (!gl->IsValid()) {
+    FML_LOG(ERROR) << "Proc table was invalid. Assuming baseline OpenGL ES";
+    return RuntimeStageBackend::kOpenGLES;
+  }
+  bool is_gles3 = gl->GetDescription()->GetGlVersion().IsAtLeast(Version(3));
+  return is_gles3 ? RuntimeStageBackend::kOpenGLES3
+                  : RuntimeStageBackend::kOpenGLES;
+}
+
 }  // namespace impeller
diff --git a/engine/src/flutter/impeller/playground/backend/gles/playground_impl_gles.h b/engine/src/flutter/impeller/playground/backend/gles/playground_impl_gles.h
index c1973e9..d54d1e7 100644
--- a/engine/src/flutter/impeller/playground/backend/gles/playground_impl_gles.h
+++ b/engine/src/flutter/impeller/playground/backend/gles/playground_impl_gles.h
@@ -42,6 +42,8 @@
   Playground::GLProcAddressResolver CreateGLProcAddressResolver()
       const override;
 
+  RuntimeStageBackend GetRuntimeStageBackend() const override;
+
   PlaygroundImplGLES(const PlaygroundImplGLES&) = delete;
 
   PlaygroundImplGLES& operator=(const PlaygroundImplGLES&) = delete;
diff --git a/engine/src/flutter/impeller/playground/backend/metal/playground_impl_mtl.h b/engine/src/flutter/impeller/playground/backend/metal/playground_impl_mtl.h
index 2028a82..4ba9a08 100644
--- a/engine/src/flutter/impeller/playground/backend/metal/playground_impl_mtl.h
+++ b/engine/src/flutter/impeller/playground/backend/metal/playground_impl_mtl.h
@@ -26,6 +26,8 @@
   fml::Status SetCapabilities(
       const std::shared_ptr<Capabilities>& capabilities) override;
 
+  RuntimeStageBackend GetRuntimeStageBackend() const override;
+
  private:
   struct Data;
 
diff --git a/engine/src/flutter/impeller/playground/backend/metal/playground_impl_mtl.mm b/engine/src/flutter/impeller/playground/backend/metal/playground_impl_mtl.mm
index 218dbe5..12c596f 100644
--- a/engine/src/flutter/impeller/playground/backend/metal/playground_impl_mtl.mm
+++ b/engine/src/flutter/impeller/playground/backend/metal/playground_impl_mtl.mm
@@ -143,4 +143,8 @@
   is_gpu_disabled_sync_switch_->SetSwitch(disabled);
 }
 
+RuntimeStageBackend PlaygroundImplMTL::GetRuntimeStageBackend() const {
+  return RuntimeStageBackend::kMetal;
+}
+
 }  // namespace impeller
diff --git a/engine/src/flutter/impeller/playground/backend/vulkan/playground_impl_vk.cc b/engine/src/flutter/impeller/playground/backend/vulkan/playground_impl_vk.cc
index 3ea803d..ab95717 100644
--- a/engine/src/flutter/impeller/playground/backend/vulkan/playground_impl_vk.cc
+++ b/engine/src/flutter/impeller/playground/backend/vulkan/playground_impl_vk.cc
@@ -246,4 +246,8 @@
   };
 }
 
+RuntimeStageBackend PlaygroundImplVK::GetRuntimeStageBackend() const {
+  return RuntimeStageBackend::kVulkan;
+}
+
 }  // namespace impeller
diff --git a/engine/src/flutter/impeller/playground/backend/vulkan/playground_impl_vk.h b/engine/src/flutter/impeller/playground/backend/vulkan/playground_impl_vk.h
index 0f40124..9ea956f 100644
--- a/engine/src/flutter/impeller/playground/backend/vulkan/playground_impl_vk.h
+++ b/engine/src/flutter/impeller/playground/backend/vulkan/playground_impl_vk.h
@@ -21,6 +21,8 @@
   fml::Status SetCapabilities(
       const std::shared_ptr<Capabilities>& capabilities) override;
 
+  RuntimeStageBackend GetRuntimeStageBackend() const override;
+
  private:
   std::shared_ptr<Context> context_;
 
diff --git a/engine/src/flutter/impeller/playground/playground.cc b/engine/src/flutter/impeller/playground/playground.cc
index 363c8ba..b105924 100644
--- a/engine/src/flutter/impeller/playground/playground.cc
+++ b/engine/src/flutter/impeller/playground/playground.cc
@@ -28,6 +28,7 @@
 #include "impeller/playground/imgui/imgui_impl_impeller.h"
 #include "impeller/playground/playground.h"
 #include "impeller/playground/playground_impl.h"
+#include "impeller/renderer/backend/gles/context_gles.h"
 #include "impeller/renderer/context.h"
 #include "impeller/renderer/render_pass.h"
 #include "third_party/imgui/backends/imgui_impl_glfw.h"
@@ -531,4 +532,8 @@
   impl_->SetGPUDisabled(value);
 }
 
+RuntimeStageBackend Playground::GetRuntimeStageBackend() const {
+  return impl_->GetRuntimeStageBackend();
+}
+
 }  // namespace impeller
diff --git a/engine/src/flutter/impeller/playground/playground.h b/engine/src/flutter/impeller/playground/playground.h
index 6878a59..e343be2 100644
--- a/engine/src/flutter/impeller/playground/playground.h
+++ b/engine/src/flutter/impeller/playground/playground.h
@@ -18,7 +18,6 @@
 #include "impeller/playground/image/decompressed_image.h"
 #include "impeller/playground/switches.h"
 #include "impeller/renderer/render_pass.h"
-#include "impeller/runtime_stage/runtime_stage.h"
 
 namespace impeller {
 
@@ -30,19 +29,6 @@
   kVulkan,
 };
 
-constexpr inline RuntimeStageBackend PlaygroundBackendToRuntimeStageBackend(
-    PlaygroundBackend backend) {
-  switch (backend) {
-    case PlaygroundBackend::kMetal:
-      return RuntimeStageBackend::kMetal;
-    case PlaygroundBackend::kOpenGLES:
-      return RuntimeStageBackend::kOpenGLES;
-    case PlaygroundBackend::kVulkan:
-      return RuntimeStageBackend::kVulkan;
-  }
-  FML_UNREACHABLE();
-}
-
 std::string PlaygroundBackendToString(PlaygroundBackend backend);
 
 class Playground {
@@ -127,6 +113,8 @@
   /// Only supported on the Metal backend.
   void SetGPUDisabled(bool disabled) const;
 
+  RuntimeStageBackend GetRuntimeStageBackend() const;
+
  protected:
   const PlaygroundSwitches switches_;
 
diff --git a/engine/src/flutter/impeller/playground/playground_impl.h b/engine/src/flutter/impeller/playground/playground_impl.h
index ed2593f..1a26d38 100644
--- a/engine/src/flutter/impeller/playground/playground_impl.h
+++ b/engine/src/flutter/impeller/playground/playground_impl.h
@@ -42,6 +42,9 @@
 
   virtual void SetGPUDisabled(bool disabled) const {}
 
+  [[nodiscard]]
+  virtual RuntimeStageBackend GetRuntimeStageBackend() const = 0;
+
  protected:
   const PlaygroundSwitches switches_;
 
diff --git a/engine/src/flutter/impeller/playground/playground_test.h b/engine/src/flutter/impeller/playground/playground_test.h
index 85a2d32..fae11be 100644
--- a/engine/src/flutter/impeller/playground/playground_test.h
+++ b/engine/src/flutter/impeller/playground/playground_test.h
@@ -11,6 +11,7 @@
 #include "flutter/testing/testing.h"
 #include "impeller/playground/playground.h"
 #include "impeller/playground/switches.h"
+#include "impeller/runtime_stage/runtime_stage.h"
 #include "third_party/abseil-cpp/absl/status/statusor.h"
 
 #if FML_OS_MACOSX
diff --git a/engine/src/flutter/impeller/renderer/backend/gles/buffer_bindings_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/buffer_bindings_gles.cc
index 34d9843..f5aab37 100644
--- a/engine/src/flutter/impeller/renderer/backend/gles/buffer_bindings_gles.cc
+++ b/engine/src/flutter/impeller/renderer/backend/gles/buffer_bindings_gles.cc
@@ -334,6 +334,9 @@
   absl::flat_hash_map<std::string, std::pair<GLint, GLuint>>::iterator it =
       ubo_locations_.find(metadata->name);
   if (it == ubo_locations_.end()) {
+    // This should only happen if we have GLESv3 but are using v2 shaders,
+    // as GLESv3 shaders compiled by impeller always have
+    // **named** uniform buffer blocks
     return BindUniformBufferV2(gl, buffer, metadata, device_buffer_gles);
   }
   const auto& [block_index, binding_point] = it->second;
diff --git a/engine/src/flutter/impeller/runtime_stage/runtime_stage_unittests.cc b/engine/src/flutter/impeller/runtime_stage/runtime_stage_unittests.cc
index 456c564..66f4a68 100644
--- a/engine/src/flutter/impeller/runtime_stage/runtime_stage_unittests.cc
+++ b/engine/src/flutter/impeller/runtime_stage/runtime_stage_unittests.cc
@@ -37,8 +37,7 @@
   ASSERT_GT(fixture->GetSize(), 0u);
   auto stages = RuntimeStage::DecodeRuntimeStages(fixture);
   ABSL_ASSERT_OK(stages);
-  auto stage =
-      stages.value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
+  auto stage = stages.value()[GetRuntimeStageBackend()];
   ASSERT_TRUE(stage);
   ASSERT_EQ(stage->GetShaderStage(), RuntimeShaderStage::kFragment);
 }
@@ -80,8 +79,7 @@
   ASSERT_GT(fixture->GetSize(), 0u);
   auto stages = RuntimeStage::DecodeRuntimeStages(fixture);
   ABSL_ASSERT_OK(stages);
-  auto stage =
-      stages.value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
+  auto stage = stages.value()[GetRuntimeStageBackend()];
 
   ASSERT_TRUE(stage);
   switch (GetBackend()) {
@@ -341,8 +339,7 @@
   ASSERT_GT(fixture->GetSize(), 0u);
   auto stages = RuntimeStage::DecodeRuntimeStages(fixture);
   ABSL_ASSERT_OK(stages);
-  auto stage =
-      stages.value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
+  auto stage = stages.value()[GetRuntimeStageBackend()];
 
   EXPECT_EQ(stage->GetUniforms().size(), 2u);
   auto uni = stage->GetUniform(RuntimeStage::kVulkanUBOName);
@@ -368,8 +365,7 @@
   ASSERT_GT(fixture->GetSize(), 0u);
   auto stages = RuntimeStage::DecodeRuntimeStages(fixture);
   ABSL_ASSERT_OK(stages);
-  auto stage =
-      stages.value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
+  auto stage = stages.value()[GetRuntimeStageBackend()];
 
   EXPECT_EQ(stage->GetUniforms().size(), 2u);
   auto uni = stage->GetUniform(RuntimeStage::kVulkanUBOName);
@@ -391,8 +387,7 @@
   ASSERT_GT(fixture->GetSize(), 0u);
   auto stages = RuntimeStage::DecodeRuntimeStages(fixture);
   ABSL_ASSERT_OK(stages);
-  auto stage =
-      stages.value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
+  auto stage = stages.value()[GetRuntimeStageBackend()];
   ASSERT_TRUE(stage);
   std::promise<bool> registration;
   auto future = registration.get_future();
@@ -424,9 +419,7 @@
 TEST_P(RuntimeStageTest, CanCreatePipelineFromRuntimeStage) {
   auto stages_result = OpenAssetAsRuntimeStage("ink_sparkle.frag.iplr");
   ABSL_ASSERT_OK(stages_result);
-  auto stage =
-      stages_result
-          .value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
+  auto stage = stages_result.value()[GetRuntimeStageBackend()];
 
   ASSERT_TRUE(stage);
   ASSERT_NE(stage, nullptr);
diff --git a/engine/src/flutter/shell/platform/embedder/BUILD.gn b/engine/src/flutter/shell/platform/embedder/BUILD.gn
index 18564dc..61abe64 100644
--- a/engine/src/flutter/shell/platform/embedder/BUILD.gn
+++ b/engine/src/flutter/shell/platform/embedder/BUILD.gn
@@ -316,6 +316,8 @@
 
     if (test_enable_gl) {
       sources += [
+        "//flutter/impeller/renderer/backend/gles/test/mock_gles.cc",
+        "//flutter/impeller/renderer/backend/gles/test/mock_gles.h",
         "tests/embedder_test_backingstore_producer_gl.cc",
         "tests/embedder_test_backingstore_producer_gl.h",
         "tests/embedder_test_compositor_gl.cc",
@@ -323,9 +325,11 @@
         "tests/embedder_test_context_gl.cc",
         "tests/embedder_test_context_gl.h",
         "tests/embedder_test_gl.cc",
+        "tests/embedder_test_surface_gl_impeller.cc",
       ]
 
       public_deps += [
+        "//flutter/impeller/renderer/backend/gles",
         "//flutter/testing:opengl",
         "//flutter/third_party/vulkan-deps/vulkan-headers/src:vulkan_headers",
       ]
diff --git a/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc b/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc
index 779c37b..d73ae89 100644
--- a/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc
+++ b/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc
@@ -10,11 +10,43 @@
 #include "impeller/entity/gles/entity_shaders_gles.h"
 #include "impeller/entity/gles/framebuffer_blend_shaders_gles.h"
 #include "impeller/entity/gles/modern_shaders_gles.h"
+#include "impeller/entity/gles3/entity_shaders_gles.h"
+#include "impeller/entity/gles3/framebuffer_blend_shaders_gles.h"
+#include "impeller/entity/gles3/modern_shaders_gles.h"
 #include "impeller/renderer/backend/gles/context_gles.h"
 #include "impeller/renderer/backend/gles/proc_table_gles.h"
 
 namespace flutter {
 
+namespace {
+std::vector<std::shared_ptr<fml::Mapping>> GetShaderMappings(bool is_gles3) {
+  if (is_gles3) {
+    return {
+        std::make_shared<fml::NonOwnedMapping>(
+            impeller_entity_shaders_gles3_data,
+            impeller_entity_shaders_gles3_length),
+        std::make_shared<fml::NonOwnedMapping>(
+            impeller_modern_shaders_gles3_data,
+            impeller_modern_shaders_gles3_length),
+        std::make_shared<fml::NonOwnedMapping>(
+            impeller_framebuffer_blend_shaders_gles3_data,
+            impeller_framebuffer_blend_shaders_gles3_length),
+    };
+  }
+  return {
+      std::make_shared<fml::NonOwnedMapping>(
+          impeller_entity_shaders_gles_data,
+          impeller_entity_shaders_gles_length),
+      std::make_shared<fml::NonOwnedMapping>(
+          impeller_modern_shaders_gles_data,
+          impeller_modern_shaders_gles_length),
+      std::make_shared<fml::NonOwnedMapping>(
+          impeller_framebuffer_blend_shaders_gles_data,
+          impeller_framebuffer_blend_shaders_gles_length),
+  };
+}
+}  // namespace
+
 class ReactorWorker final : public impeller::ReactorGLES::Worker {
  public:
   ReactorWorker() = default;
@@ -63,23 +95,16 @@
   // state can be accessed.
   gl_dispatch_table_.gl_make_current_callback();
 
-  std::vector<std::shared_ptr<fml::Mapping>> shader_mappings = {
-      std::make_shared<fml::NonOwnedMapping>(
-          impeller_entity_shaders_gles_data,
-          impeller_entity_shaders_gles_length),
-      std::make_shared<fml::NonOwnedMapping>(
-          impeller_modern_shaders_gles_data,
-          impeller_modern_shaders_gles_length),
-      std::make_shared<fml::NonOwnedMapping>(
-          impeller_framebuffer_blend_shaders_gles_data,
-          impeller_framebuffer_blend_shaders_gles_length),
-  };
   auto gl = std::make_unique<impeller::ProcTableGLES>(
       gl_dispatch_table_.gl_proc_resolver);
   if (!gl->IsValid()) {
     return;
   }
 
+  const auto is_gles3 =
+      gl->GetDescription()->GetGlVersion().IsAtLeast(impeller::Version(3));
+  const auto shader_mappings = GetShaderMappings(is_gles3);
+
   impeller_context_ = impeller::ContextGLES::Create(
       impeller::Flags{}, std::move(gl), shader_mappings,
       /*enable_gpu_tracing=*/false);
diff --git a/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.h b/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.h
index 52febc2..274e1cc 100644
--- a/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.h
+++ b/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.h
@@ -16,6 +16,11 @@
 }  // namespace impeller
 
 namespace flutter {
+namespace testing {
+FML_TEST_CLASS(EmbedderSurfaceGLImpellerTest, GLES3ContextHasGLES3Shaders);
+FML_TEST_CLASS(EmbedderSurfaceGLImpellerTest,
+               GLES2ContextDoesNotHaveGLES3Shaders);
+}  // namespace testing
 
 class ReactorWorker;
 
@@ -30,6 +35,10 @@
   ~EmbedderSurfaceGLImpeller() override;
 
  private:
+  FML_FRIEND_TEST(testing::EmbedderSurfaceGLImpellerTest,
+                  GLES3ContextHasGLES3Shaders);
+  FML_FRIEND_TEST(testing::EmbedderSurfaceGLImpellerTest,
+                  GLES2ContextDoesNotHaveGLES3Shaders);
   bool valid_ = false;
   EmbedderSurfaceGLSkia::GLDispatchTable gl_dispatch_table_;
   bool fbo_reset_after_present_;
diff --git a/engine/src/flutter/shell/platform/embedder/tests/embedder_test_surface_gl_impeller.cc b/engine/src/flutter/shell/platform/embedder/tests/embedder_test_surface_gl_impeller.cc
new file mode 100644
index 0000000..eda11d2
--- /dev/null
+++ b/engine/src/flutter/shell/platform/embedder/tests/embedder_test_surface_gl_impeller.cc
@@ -0,0 +1,81 @@
+// 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 "flatbuffers/stl_emulation.h"
+#include "flutter/impeller/renderer/backend/gles/test/mock_gles.h"
+#include "flutter/shell/platform/embedder/embedder_surface_gl_impeller.h"
+#include "impeller/renderer/backend/gles/shader_function_gles.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace flutter {
+namespace testing {
+
+using ::testing::Not;
+using ::testing::StartsWith;
+
+namespace {
+EmbedderSurfaceGLSkia::GLDispatchTable StubDispatchTable(
+    std::string_view version) {
+  impeller::testing::MockGLES::Init(flatbuffers::nullopt, version.data());
+  static constexpr auto dummy_always_true = [] { return true; };
+  return EmbedderSurfaceGLSkia::GLDispatchTable{
+      .gl_make_current_callback = dummy_always_true,
+      .gl_clear_current_callback = dummy_always_true,
+      .gl_present_callback = [](const auto) { return true; },
+      .gl_fbo_callback = [](const auto) { return 0; },
+      .gl_make_resource_current_callback = dummy_always_true,
+      .gl_surface_transformation_callback = [] { return DlMatrix{}; },
+      .gl_proc_resolver = impeller::testing::kMockResolverGLES,
+      .gl_populate_existing_damage = [](const auto) { return GLFBOInfo{}; },
+  };
+}
+}  // namespace
+
+TEST(EmbedderSurfaceGLImpellerTest, GLES3ContextHasGLES3Shaders) {
+  const auto gl_dispatch_table =
+      StubDispatchTable(/* version */ "OpenGL ES 3.0");
+  const auto surface = EmbedderSurfaceGLImpeller(
+      gl_dispatch_table, /* fbo_reset_after_present */ false,
+      /* external_view_embedder */ nullptr);
+
+  const std::shared_ptr<impeller::Context> context =
+      surface.CreateImpellerContext();
+  const std::shared_ptr<impeller::ShaderLibrary> shaders =
+      context->GetShaderLibrary();
+  const std::shared_ptr<const impeller::ShaderFunction> func =
+      shaders->GetFunction("imp_line_fragment_main",
+                           impeller::ShaderStage::kFragment);
+  const auto gles_func = impeller::ShaderFunctionGLES::Cast(func.get());
+  const std::shared_ptr<const fml::Mapping> source =
+      gles_func->GetSourceMapping();
+  const auto text =
+      std::string_view(reinterpret_cast<const char*>(source->GetMapping()));
+  EXPECT_THAT(text, StartsWith("#version 300 es"));
+}
+
+TEST(EmbedderSurfaceGLImpellerTest, GLES2ContextDoesNotHaveGLES3Shaders) {
+  const auto gl_dispatch_table =
+      StubDispatchTable(/* version */ "OpenGL ES 2.0");
+  const auto surface = EmbedderSurfaceGLImpeller(
+      gl_dispatch_table, /* fbo_reset_after_present */ false,
+      /* external_view_embedder */ nullptr);
+
+  const std::shared_ptr<impeller::Context> context =
+      surface.CreateImpellerContext();
+  const std::shared_ptr<impeller::ShaderLibrary> shaders =
+      context->GetShaderLibrary();
+  const std::shared_ptr<const impeller::ShaderFunction> func =
+      shaders->GetFunction("imp_line_fragment_main",
+                           impeller::ShaderStage::kFragment);
+  const auto gles_func = impeller::ShaderFunctionGLES::Cast(func.get());
+  const std::shared_ptr<const fml::Mapping> source =
+      gles_func->GetSourceMapping();
+  const auto text =
+      std::string_view(reinterpret_cast<const char*>(source->GetMapping()));
+  EXPECT_THAT(text, StartsWith("#version 100"));
+}
+}  // namespace testing
+}  // namespace flutter