[Impeller] flutter_tester --enable-impeller (#46389)

This patch does the following:

- Updates `flutter_tester` to set up an Impeller rendering context and surface if `--enable-impeller` is set to true, using the Vulkan backend with Swiftshader.
- Updates `run_tests.py` to run all tests except the smoke test (that one really has no rendering impact whatsoever) with and without `--enable-impeller`.
- Updates a few tests to work that were trivial:
  - A couple tests needed updated goldens for very minor rendering differences. Filed https://github.com/flutter/flutter/issues/135684 to track using Skia gold for this instead.
  - Disabled SKP screenshotting if Impeller is enabled, and updated the test checking that to verify an error is thrown if an SKP is requested.
  - The Dart GPU based test now asserts that the gpu context is available if Impeller is enabled, and does not deadlock if run in a single threaded mode.
  - We were missing some trace events around `Canvas::SaveLayer` for Impeller as compared to Skia.
  - A couple other tests had strict checks about exception messages that are slightly different between Skia and Impeller.
- I've filed bugs for other tests that may require a little more work, and skipped them for now. For FragmentProgram on Vulkan I reused an existing bug.

This is part of my attempt to address https://github.com/flutter/flutter/issues/135693, although @chinmaygarde and I had slightly different ideas about how to do this.

The goals here are:

- Run the Dart unit tests we already have with Impeller enabled.
- Enable running more of the framework tests (including gold tests) with Impeller enabled.
- Run all of these tests via public `dart:ui` API rather than mucking around in C++ internals in the engine.
diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter
index 92979f5..589e361 100644
--- a/ci/licenses_golden/licenses_flutter
+++ b/ci/licenses_golden/licenses_flutter
@@ -4043,6 +4043,7 @@
 ORIGIN: ../../../flutter/vulkan/procs/vulkan_interface.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/vulkan/procs/vulkan_proc_table.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/vulkan/procs/vulkan_proc_table.h + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/vulkan/swiftshader_path.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/vulkan/vulkan_application.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/vulkan/vulkan_application.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/vulkan/vulkan_backbuffer.cc + ../../../flutter/LICENSE
@@ -6828,6 +6829,7 @@
 FILE: ../../../flutter/vulkan/procs/vulkan_interface.h
 FILE: ../../../flutter/vulkan/procs/vulkan_proc_table.cc
 FILE: ../../../flutter/vulkan/procs/vulkan_proc_table.h
+FILE: ../../../flutter/vulkan/swiftshader_path.h
 FILE: ../../../flutter/vulkan/vulkan_application.cc
 FILE: ../../../flutter/vulkan/vulkan_application.h
 FILE: ../../../flutter/vulkan/vulkan_backbuffer.cc
diff --git a/impeller/BUILD.gn b/impeller/BUILD.gn
index 696226f..c02ccd0 100644
--- a/impeller/BUILD.gn
+++ b/impeller/BUILD.gn
@@ -34,6 +34,10 @@
     defines += [ "IMPELLER_ENABLE_VULKAN=1" ]
   }
 
+  if (impeller_enable_vulkan_playgrounds) {
+    defines += [ "IMPELLER_ENABLE_VULKAN_PLAYGROUNDS=1" ]
+  }
+
   if (impeller_trace_all_gl_calls) {
     defines += [ "IMPELLER_TRACE_ALL_GL_CALLS" ]
   }
diff --git a/impeller/aiks/canvas.cc b/impeller/aiks/canvas.cc
index d4d3829..2308d53 100644
--- a/impeller/aiks/canvas.cc
+++ b/impeller/aiks/canvas.cc
@@ -8,6 +8,7 @@
 #include <utility>
 
 #include "flutter/fml/logging.h"
+#include "flutter/fml/trace_event.h"
 #include "impeller/aiks/image_filter.h"
 #include "impeller/aiks/paint_pass_delegate.h"
 #include "impeller/entity/contents/atlas_contents.h"
@@ -536,6 +537,7 @@
 void Canvas::SaveLayer(const Paint& paint,
                        std::optional<Rect> bounds,
                        const std::shared_ptr<ImageFilter>& backdrop_filter) {
+  TRACE_EVENT0("flutter", "Canvas::saveLayer");
   Save(true, paint.blend_mode, backdrop_filter);
 
   auto& new_layer_pass = GetCurrentPass();
diff --git a/impeller/base/validation.cc b/impeller/base/validation.cc
index bb41c50..bbfd7ef 100644
--- a/impeller/base/validation.cc
+++ b/impeller/base/validation.cc
@@ -48,7 +48,7 @@
   } else {
     FML_LOG(ERROR) << stream.str();
   }
-#endif  // IMPELLER_DEBUG
+#endif  // IMPELLER_ENABLE_VALIDATION
 }
 
 }  // namespace impeller
diff --git a/impeller/playground/playground.cc b/impeller/playground/playground.cc
index 93eb4eb..8d2f591 100644
--- a/impeller/playground/playground.cc
+++ b/impeller/playground/playground.cc
@@ -102,7 +102,7 @@
       return false;
 #endif  // IMPELLER_ENABLE_OPENGLES
     case PlaygroundBackend::kVulkan:
-#if IMPELLER_ENABLE_VULKAN
+#if IMPELLER_ENABLE_VULKAN && IMPELLER_ENABLE_VULKAN_PLAYGROUNDS
       return true;
 #else   // IMPELLER_ENABLE_VULKAN
       return false;
diff --git a/impeller/renderer/backend/vulkan/allocator_vk.cc b/impeller/renderer/backend/vulkan/allocator_vk.cc
index 3fc4f2f..f90ee4b 100644
--- a/impeller/renderer/backend/vulkan/allocator_vk.cc
+++ b/impeller/renderer/backend/vulkan/allocator_vk.cc
@@ -265,6 +265,7 @@
                            vk::Device device,
                            bool supports_memoryless_textures)
       : TextureSourceVK(desc), resource_(std::move(resource_manager)) {
+    FML_DCHECK(desc.format != PixelFormat::kUnknown);
     TRACE_EVENT0("impeller", "CreateDeviceTexture");
     vk::ImageCreateInfo image_info;
     image_info.flags = ToVKImageCreateFlags(desc.type);
diff --git a/impeller/renderer/backend/vulkan/blit_command_vk_unittests.cc b/impeller/renderer/backend/vulkan/blit_command_vk_unittests.cc
index 9b911c6..260c9e4 100644
--- a/impeller/renderer/backend/vulkan/blit_command_vk_unittests.cc
+++ b/impeller/renderer/backend/vulkan/blit_command_vk_unittests.cc
@@ -16,9 +16,11 @@
   auto encoder = std::make_unique<CommandEncoderFactoryVK>(context)->Create();
   BlitCopyTextureToTextureCommandVK cmd;
   cmd.source = context->GetResourceAllocator()->CreateTexture({
+      .format = PixelFormat::kR8G8B8A8UNormInt,
       .size = ISize(100, 100),
   });
   cmd.destination = context->GetResourceAllocator()->CreateTexture({
+      .format = PixelFormat::kR8G8B8A8UNormInt,
       .size = ISize(100, 100),
   });
   bool result = cmd.Encode(*encoder.get());
@@ -32,6 +34,7 @@
   auto encoder = std::make_unique<CommandEncoderFactoryVK>(context)->Create();
   BlitCopyTextureToBufferCommandVK cmd;
   cmd.source = context->GetResourceAllocator()->CreateTexture({
+      .format = PixelFormat::kR8G8B8A8UNormInt,
       .size = ISize(100, 100),
   });
   cmd.destination = context->GetResourceAllocator()->CreateBuffer({
@@ -48,6 +51,7 @@
   auto encoder = std::make_unique<CommandEncoderFactoryVK>(context)->Create();
   BlitCopyBufferToTextureCommandVK cmd;
   cmd.destination = context->GetResourceAllocator()->CreateTexture({
+      .format = PixelFormat::kR8G8B8A8UNormInt,
       .size = ISize(100, 100),
   });
   cmd.source = context->GetResourceAllocator()
@@ -66,6 +70,7 @@
   auto encoder = std::make_unique<CommandEncoderFactoryVK>(context)->Create();
   BlitGenerateMipmapCommandVK cmd;
   cmd.texture = context->GetResourceAllocator()->CreateTexture({
+      .format = PixelFormat::kR8G8B8A8UNormInt,
       .size = ISize(100, 100),
       .mip_count = 2,
   });
diff --git a/impeller/renderer/backend/vulkan/surface_context_vk.cc b/impeller/renderer/backend/vulkan/surface_context_vk.cc
index 2e20d2a..afc1407 100644
--- a/impeller/renderer/backend/vulkan/surface_context_vk.cc
+++ b/impeller/renderer/backend/vulkan/surface_context_vk.cc
@@ -63,6 +63,10 @@
     VALIDATION_LOG << "Could not create swapchain.";
     return false;
   }
+  if (!swapchain->IsValid()) {
+    VALIDATION_LOG << "Could not create valid swapchain.";
+    return false;
+  }
   swapchain_ = std::move(swapchain);
   return true;
 }
diff --git a/impeller/renderer/backend/vulkan/swapchain_impl_vk.cc b/impeller/renderer/backend/vulkan/swapchain_impl_vk.cc
index 0af8800..dcd5d1c 100644
--- a/impeller/renderer/backend/vulkan/swapchain_impl_vk.cc
+++ b/impeller/renderer/backend/vulkan/swapchain_impl_vk.cc
@@ -142,6 +142,7 @@
     vk::SwapchainKHR old_swapchain,
     vk::SurfaceTransformFlagBitsKHR last_transform) {
   if (!context) {
+    VALIDATION_LOG << "Cannot create a swapchain without a context.";
     return;
   }
 
diff --git a/impeller/renderer/backend/vulkan/swapchain_vk.cc b/impeller/renderer/backend/vulkan/swapchain_vk.cc
index 0a09a9e..c5399ce 100644
--- a/impeller/renderer/backend/vulkan/swapchain_vk.cc
+++ b/impeller/renderer/backend/vulkan/swapchain_vk.cc
@@ -5,6 +5,7 @@
 #include "impeller/renderer/backend/vulkan/swapchain_vk.h"
 
 #include "flutter/fml/trace_event.h"
+#include "impeller/base/validation.h"
 #include "impeller/renderer/backend/vulkan/swapchain_impl_vk.h"
 
 namespace impeller {
@@ -14,6 +15,7 @@
     vk::UniqueSurfaceKHR surface) {
   auto impl = SwapchainImplVK::Create(context, std::move(surface));
   if (!impl || !impl->IsValid()) {
+    VALIDATION_LOG << "Failed to create SwapchainVK implementation.";
     return nullptr;
   }
   return std::shared_ptr<SwapchainVK>(new SwapchainVK(std::move(impl)));
diff --git a/impeller/tools/impeller.gni b/impeller/tools/impeller.gni
index 35758f3..15caa56 100644
--- a/impeller/tools/impeller.gni
+++ b/impeller/tools/impeller.gni
@@ -22,7 +22,14 @@
       (is_linux || is_win || is_android) && target_os != "fuchsia"
 
   # Whether the Vulkan backend is enabled.
-  impeller_enable_vulkan =
+  impeller_enable_vulkan = (is_linux || is_win || is_android ||
+                            enable_unittests) && target_os != "fuchsia"
+
+  # Whether playgrounds should run with Vulkan.
+  #
+  # impeller_enable_vulkan may be true in build environments that run tests but
+  # do not have a Vulkan ICD present.
+  impeller_enable_vulkan_playgrounds =
       (is_linux || is_win || is_android) && target_os != "fuchsia"
 
   # Whether to use a prebuilt impellerc.
diff --git a/lib/gpu/context.cc b/lib/gpu/context.cc
index 3441832..7539f0c 100644
--- a/lib/gpu/context.cc
+++ b/lib/gpu/context.cc
@@ -55,7 +55,8 @@
     // Grab the Impeller context from the IO manager.
     std::promise<std::shared_ptr<impeller::Context>> context_promise;
     auto impeller_context_future = context_promise.get_future();
-    dart_state->GetTaskRunners().GetIOTaskRunner()->PostTask(
+    fml::TaskRunner::RunNowOrPostTask(
+        dart_state->GetTaskRunners().GetIOTaskRunner(),
         fml::MakeCopyable([promise = std::move(context_promise),
                            io_manager = dart_state->GetIOManager()]() mutable {
           promise.set_value(io_manager ? io_manager->GetImpellerContext()
diff --git a/lib/ui/fixtures/impeller_2_dispose_op_restore_previous.apng.67.png b/lib/ui/fixtures/impeller_2_dispose_op_restore_previous.apng.67.png
new file mode 100644
index 0000000..bdec9bc
--- /dev/null
+++ b/lib/ui/fixtures/impeller_2_dispose_op_restore_previous.apng.67.png
Binary files differ
diff --git a/lib/ui/fixtures/impeller_2_dispose_op_restore_previous.apng.68.png b/lib/ui/fixtures/impeller_2_dispose_op_restore_previous.apng.68.png
new file mode 100644
index 0000000..6ade80e
--- /dev/null
+++ b/lib/ui/fixtures/impeller_2_dispose_op_restore_previous.apng.68.png
Binary files differ
diff --git a/lib/ui/fixtures/impeller_2_dispose_op_restore_previous.apng.69.png b/lib/ui/fixtures/impeller_2_dispose_op_restore_previous.apng.69.png
new file mode 100644
index 0000000..c3006d0
--- /dev/null
+++ b/lib/ui/fixtures/impeller_2_dispose_op_restore_previous.apng.69.png
Binary files differ
diff --git a/lib/ui/fixtures/impeller_four_frame_with_reuse_end.png b/lib/ui/fixtures/impeller_four_frame_with_reuse_end.png
new file mode 100644
index 0000000..7c67236
--- /dev/null
+++ b/lib/ui/fixtures/impeller_four_frame_with_reuse_end.png
Binary files differ
diff --git a/lib/ui/fixtures/impeller_heart_end.png b/lib/ui/fixtures/impeller_heart_end.png
new file mode 100644
index 0000000..cd33ff0
--- /dev/null
+++ b/lib/ui/fixtures/impeller_heart_end.png
Binary files differ
diff --git a/shell/common/shell.cc b/shell/common/shell.cc
index fc00630..a6ba487 100644
--- a/shell/common/shell.cc
+++ b/shell/common/shell.cc
@@ -1695,6 +1695,11 @@
     const ServiceProtocol::Handler::ServiceProtocolMap& params,
     rapidjson::Document* response) {
   FML_DCHECK(task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread());
+  if (settings_.enable_impeller) {
+    ServiceProtocolFailureError(
+        response, "Cannot capture SKP screenshot with Impeller enabled.");
+    return false;
+  }
   auto screenshot = rasterizer_->ScreenshotLastLayerTree(
       Rasterizer::ScreenshotType::SkiaPicture, true);
   if (screenshot.data) {
diff --git a/shell/common/shell_test_platform_view_vulkan.cc b/shell/common/shell_test_platform_view_vulkan.cc
index 3817011..d19e5cb 100644
--- a/shell/common/shell_test_platform_view_vulkan.cc
+++ b/shell/common/shell_test_platform_view_vulkan.cc
@@ -17,12 +17,8 @@
 
 #if OS_FUCHSIA
 #define VULKAN_SO_PATH "libvulkan.so"
-#elif FML_OS_MACOSX
-#define VULKAN_SO_PATH "libvk_swiftshader.dylib"
-#elif FML_OS_WIN
-#define VULKAN_SO_PATH "vk_swiftshader.dll"
 #else
-#define VULKAN_SO_PATH "libvk_swiftshader.so"
+#include "flutter/vulkan/swiftshader_path.h"
 #endif
 
 namespace flutter {
diff --git a/shell/gpu/gpu_surface_vulkan.cc b/shell/gpu/gpu_surface_vulkan.cc
index a8976d6..63f1359 100644
--- a/shell/gpu/gpu_surface_vulkan.cc
+++ b/shell/gpu/gpu_surface_vulkan.cc
@@ -104,6 +104,7 @@
     const VkImage image,
     const VkFormat format,
     const SkISize& size) {
+#ifdef SK_VULKAN
   GrVkImageInfo image_info = {
       .fImage = image,
       .fImageTiling = VK_IMAGE_TILING_OPTIMAL,
@@ -130,6 +131,9 @@
       SkColorSpace::MakeSRGB(),     // color space
       &surface_properties           // surface properties
   );
+#else
+  return nullptr;
+#endif  // SK_VULKAN
 }
 
 SkColorType GPUSurfaceVulkan::ColorTypeFromFormat(const VkFormat format) {
diff --git a/shell/gpu/gpu_surface_vulkan_impeller.cc b/shell/gpu/gpu_surface_vulkan_impeller.cc
index 3b6ec9d..917b5f0 100644
--- a/shell/gpu/gpu_surface_vulkan_impeller.cc
+++ b/shell/gpu/gpu_surface_vulkan_impeller.cc
@@ -60,6 +60,11 @@
   auto& context_vk = impeller::SurfaceContextVK::Cast(*impeller_context_);
   std::unique_ptr<impeller::Surface> surface = context_vk.AcquireNextSurface();
 
+  if (!surface) {
+    FML_LOG(ERROR) << "No surface available.";
+    return nullptr;
+  }
+
   SurfaceFrame::SubmitCallback submit_callback =
       fml::MakeCopyable([renderer = impeller_renderer_,  //
                          aiks_context = aiks_context_,   //
diff --git a/shell/testing/BUILD.gn b/shell/testing/BUILD.gn
index ec6400c..8f45723 100644
--- a/shell/testing/BUILD.gn
+++ b/shell/testing/BUILD.gn
@@ -3,6 +3,15 @@
 # found in the LICENSE file.
 
 import("//build/fuchsia/sdk.gni")
+import("//flutter/impeller/tools/impeller.gni")
+import("//flutter/shell/gpu/gpu.gni")
+
+shell_gpu_configuration("tester_gpu_configuration") {
+  enable_software = true
+  enable_gl = true
+  enable_vulkan = true
+  enable_metal = false
+}
 
 executable("testing") {
   output_name = "flutter_tester"
@@ -13,8 +22,9 @@
   ]
 
   sources = [ "tester_main.cc" ]
+  libs = []
   if (is_win) {
-    libs = [
+    libs += [
       "psapi.lib",
       "user32.lib",
       "FontSub.lib",
@@ -36,6 +46,14 @@
     "//third_party/skia",
   ]
 
+  if (impeller_supports_rendering) {
+    deps += [
+      ":tester_gpu_configuration",
+      "//flutter/impeller",
+      "//third_party/swiftshader",
+    ]
+  }
+
   metadata = {
     entitlement_file_path = [ "flutter_tester" ]
   }
diff --git a/shell/testing/tester_main.cc b/shell/testing/tester_main.cc
index 310ce62..9c0059b 100644
--- a/shell/testing/tester_main.cc
+++ b/shell/testing/tester_main.cc
@@ -29,6 +29,46 @@
 #include "third_party/dart/runtime/include/dart_api.h"
 #include "third_party/skia/include/core/SkSurface.h"
 
+#if IMPELLER_SUPPORTS_RENDERING
+#include <vulkan/vulkan.h>                                        // nogncheck
+#include "flutter/vulkan/procs/vulkan_proc_table.h"               // nogncheck
+#include "flutter/vulkan/swiftshader_path.h"                      // nogncheck
+#include "impeller/entity/vk/entity_shaders_vk.h"                 // nogncheck
+#include "impeller/entity/vk/modern_shaders_vk.h"                 // nogncheck
+#include "impeller/renderer/backend/vulkan/context_vk.h"          // nogncheck
+#include "impeller/renderer/backend/vulkan/surface_context_vk.h"  // nogncheck
+#include "impeller/renderer/context.h"                            // nogncheck
+#include "impeller/renderer/vk/compute_shaders_vk.h"              // nogncheck
+#include "shell/gpu/gpu_surface_vulkan_impeller.h"                // nogncheck
+#if IMPELLER_ENABLE_3D
+#include "impeller/scene/shaders/vk/scene_shaders_vk.h"  // nogncheck
+#endif                                                   // IMPELLER_ENABLE_3D
+
+static std::vector<std::shared_ptr<fml::Mapping>> ShaderLibraryMappings() {
+  return {
+    std::make_shared<fml::NonOwnedMapping>(impeller_entity_shaders_vk_data,
+                                           impeller_entity_shaders_vk_length),
+        std::make_shared<fml::NonOwnedMapping>(
+            impeller_modern_shaders_vk_data, impeller_modern_shaders_vk_length),
+#if IMPELLER_ENABLE_3D
+        std::make_shared<fml::NonOwnedMapping>(
+            impeller_scene_shaders_vk_data, impeller_scene_shaders_vk_length),
+#endif  // IMPELLER_ENABLE_3D
+        std::make_shared<fml::NonOwnedMapping>(
+            impeller_compute_shaders_vk_data,
+            impeller_compute_shaders_vk_length),
+  };
+}
+
+struct ImpellerVulkanContextHolder {
+  fml::RefPtr<vulkan::VulkanProcTable> vulkan_proc_table;
+  std::shared_ptr<impeller::ContextVK> context;
+  std::shared_ptr<impeller::SurfaceContextVK> surface_context;
+};
+#else
+struct ImpellerVulkanContextHolder {};
+#endif  // IMPELLER_SUPPORTS_RENDERING
+
 #if defined(FML_OS_WIN)
 #include <combaseapi.h>
 #endif  // defined(FML_OS_WIN)
@@ -81,11 +121,41 @@
 class TesterPlatformView : public PlatformView,
                            public GPUSurfaceSoftwareDelegate {
  public:
-  TesterPlatformView(Delegate& delegate, const TaskRunners& task_runners)
-      : PlatformView(delegate, task_runners) {}
+  TesterPlatformView(Delegate& delegate,
+                     const TaskRunners& task_runners,
+                     ImpellerVulkanContextHolder impeller_context_holder)
+      : PlatformView(delegate, task_runners),
+        impeller_context_holder_(std::move(impeller_context_holder)) {}
+
+  ~TesterPlatformView() {
+#if IMPELLER_SUPPORTS_RENDERING
+    if (impeller_context_holder_.context) {
+      impeller_context_holder_.context->Shutdown();
+    }
+#endif
+  }
+
+  // |PlatformView|
+  std::shared_ptr<impeller::Context> GetImpellerContext() const override {
+#if IMPELLER_SUPPORTS_RENDERING
+    return std::static_pointer_cast<impeller::Context>(
+        impeller_context_holder_.context);
+#else
+    return nullptr;
+#endif  // IMPELLER_SUPPORTS_RENDERING
+  }
 
   // |PlatformView|
   std::unique_ptr<Surface> CreateRenderingSurface() override {
+#if IMPELLER_SUPPORTS_RENDERING
+    if (delegate_.OnPlatformViewGetSettings().enable_impeller) {
+      FML_DCHECK(impeller_context_holder_.context);
+      auto surface = std::make_unique<GPUSurfaceVulkanImpeller>(
+          impeller_context_holder_.surface_context);
+      FML_DCHECK(surface->IsValid());
+      return surface;
+    }
+#endif  // IMPELLER_SUPPORTS_RENDERING
     auto surface = std::make_unique<TesterGPUSurfaceSoftware>(
         this, true /* render to surface */);
     FML_DCHECK(surface->IsValid());
@@ -126,6 +196,7 @@
 
  private:
   sk_sp<SkSurface> sk_surface_ = nullptr;
+  [[maybe_unused]] ImpellerVulkanContextHolder impeller_context_holder_;
   std::shared_ptr<TesterExternalViewEmbedder> external_view_embedder_ =
       std::make_shared<TesterExternalViewEmbedder>();
 };
@@ -235,10 +306,63 @@
                                           io_task_runner         // io
   );
 
+  ImpellerVulkanContextHolder impeller_context_holder;
+
+#if IMPELLER_SUPPORTS_RENDERING
+  if (settings.enable_impeller) {
+    impeller_context_holder.vulkan_proc_table =
+        fml::MakeRefCounted<vulkan::VulkanProcTable>(VULKAN_SO_PATH);
+    if (!impeller_context_holder.vulkan_proc_table
+             ->NativeGetInstanceProcAddr()) {
+      FML_LOG(ERROR) << "Could not load Swiftshader library.";
+      return EXIT_FAILURE;
+    }
+    impeller::ContextVK::Settings context_settings;
+    context_settings.proc_address_callback =
+        impeller_context_holder.vulkan_proc_table->NativeGetInstanceProcAddr();
+    context_settings.shader_libraries_data = ShaderLibraryMappings();
+    context_settings.cache_directory = fml::paths::GetCachesDirectory();
+    context_settings.enable_validation = settings.enable_vulkan_validation;
+
+    impeller_context_holder.context =
+        impeller::ContextVK::Create(std::move(context_settings));
+    if (!impeller_context_holder.context ||
+        !impeller_context_holder.context->IsValid()) {
+      VALIDATION_LOG << "Could not create Vulkan context.";
+      return EXIT_FAILURE;
+    }
+
+    impeller::vk::SurfaceKHR vk_surface;
+    impeller::vk::HeadlessSurfaceCreateInfoEXT surface_create_info;
+    auto res =
+        impeller_context_holder.context->GetInstance().createHeadlessSurfaceEXT(
+            &surface_create_info,  // surface create info
+            nullptr,               // allocator
+            &vk_surface            // surface
+        );
+    if (res != impeller::vk::Result::eSuccess) {
+      VALIDATION_LOG << "Could not create surface for tester "
+                     << impeller::vk::to_string(res);
+      return EXIT_FAILURE;
+    }
+
+    impeller::vk::UniqueSurfaceKHR surface{
+        vk_surface, impeller_context_holder.context->GetInstance()};
+    impeller_context_holder.surface_context =
+        impeller_context_holder.context->CreateSurfaceContext();
+    if (!impeller_context_holder.surface_context->SetWindowSurface(
+            std::move(surface))) {
+      VALIDATION_LOG << "Could not set up surface for context.";
+      return EXIT_FAILURE;
+    }
+  }
+#endif  // IMPELLER_SUPPORTS_RENDERING
+
   Shell::CreateCallback<PlatformView> on_create_platform_view =
-      [](Shell& shell) {
-        return std::make_unique<TesterPlatformView>(shell,
-                                                    shell.GetTaskRunners());
+      [impeller_context_holder =
+           std::move(impeller_context_holder)](Shell& shell) {
+        return std::make_unique<TesterPlatformView>(
+            shell, shell.GetTaskRunners(), impeller_context_holder);
       };
 
   Shell::CreateCallback<Rasterizer> on_create_rasterizer = [](Shell& shell) {
diff --git a/testing/dart/canvas_test.dart b/testing/dart/canvas_test.dart
index 6732423..d317e2f 100644
--- a/testing/dart/canvas_test.dart
+++ b/testing/dart/canvas_test.dart
@@ -12,6 +12,8 @@
 import 'package:path/path.dart' as path;
 import 'package:vector_math/vector_math_64.dart';
 
+import 'impeller_enabled.dart';
+
 typedef CanvasCallback = void Function(Canvas canvas);
 
 Future<Image> createImage(int width, int height) {
@@ -192,7 +194,7 @@
     final bool areEqual =
         await fuzzyGoldenImageCompare(image, 'canvas_test_toImage.png');
     expect(areEqual, true);
-  });
+  }, skip: impellerEnabled);
 
   Gradient makeGradient() {
     return Gradient.linear(
@@ -213,7 +215,7 @@
     final bool areEqual =
         await fuzzyGoldenImageCompare(image, 'canvas_test_dithered_gradient.png');
     expect(areEqual, true);
-  }, skip: !Platform.isLinux); // https://github.com/flutter/flutter/issues/53784
+  }, skip: !Platform.isLinux || impellerEnabled); // https://github.com/flutter/flutter/issues/53784
 
   test('Null values allowed for drawAtlas methods', () async {
     final Image image = await createImage(100, 100);
@@ -349,7 +351,7 @@
     final bool areEqual =
         await fuzzyGoldenImageCompare(image, 'dotted_path_effect_mixed_with_stroked_geometry.png');
     expect(areEqual, true);
-  }, skip: !Platform.isLinux); // https://github.com/flutter/flutter/issues/53784
+  }, skip: !Platform.isLinux || impellerEnabled); // https://github.com/flutter/flutter/issues/53784
 
   test('Gradients with matrices in Paragraphs render correctly', () async {
     final Image image = await toImage((Canvas canvas) {
@@ -401,7 +403,7 @@
     final bool areEqual =
     await fuzzyGoldenImageCompare(image, 'text_with_gradient_with_matrix.png');
     expect(areEqual, true);
-  }, skip: !Platform.isLinux); // https://github.com/flutter/flutter/issues/53784
+  }, skip: !Platform.isLinux || impellerEnabled); // https://github.com/flutter/flutter/issues/53784
 
   test('toImageSync - too big', () async {
     PictureRecorder recorder = PictureRecorder();
@@ -417,6 +419,12 @@
     recorder = PictureRecorder();
     canvas = Canvas(recorder);
 
+    if (impellerEnabled) {
+      // Impeller tries to automagically scale this. See
+      // https://github.com/flutter/flutter/issues/128885
+      canvas.drawImage(image, Offset.zero, Paint());
+      return;
+    }
     // On a slower CI machine, the raster thread may get behind the UI thread
     // here. However, once the image is in an error state it will immediately
     // throw on subsequent attempts.
diff --git a/testing/dart/codec_test.dart b/testing/dart/codec_test.dart
index fb07a63..702a5fd 100644
--- a/testing/dart/codec_test.dart
+++ b/testing/dart/codec_test.dart
@@ -9,6 +9,8 @@
 import 'package:litetest/litetest.dart';
 import 'package:path/path.dart' as path;
 
+import 'impeller_enabled.dart';
+
 void main() {
 
   test('Animation metadata', () async {
@@ -43,7 +45,11 @@
       await codec.getNextFrame();
       fail('exception not thrown');
     } on Exception catch (e) {
-      expect(e.toString(), contains('Codec failed'));
+      if (impellerEnabled) {
+        expect(e.toString(), contains('Could not decompress image.'));
+      } else {
+        expect(e.toString(), contains('Codec failed'));
+      }
     }
   });
 
@@ -145,8 +151,9 @@
     final ui.Image image = frameInfo.image;
     final ByteData imageData = (await image.toByteData(format: ui.ImageByteFormat.png))!;
 
+    final String fileName = impellerEnabled ? 'impeller_four_frame_with_reuse_end.png' : 'four_frame_with_reuse_end.png';
     final Uint8List goldenData = File(
-      path.join('flutter', 'lib', 'ui', 'fixtures', 'four_frame_with_reuse_end.png'),
+      path.join('flutter', 'lib', 'ui', 'fixtures', fileName),
     ).readAsBytesSync();
 
     expect(imageData.buffer.asUint8List(), goldenData);
@@ -170,8 +177,10 @@
     final ui.Image image = frameInfo.image;
     final ByteData imageData = (await image.toByteData(format: ui.ImageByteFormat.png))!;
 
+    final String fileName = impellerEnabled ? 'impeller_heart_end.png' : 'heart_end.png';
+
     final Uint8List goldenData = File(
-      path.join('flutter', 'lib', 'ui', 'fixtures', 'heart_end.png'),
+      path.join('flutter', 'lib', 'ui', 'fixtures', fileName),
     ).readAsBytesSync();
 
     expect(imageData.buffer.asUint8List(), goldenData);
@@ -194,8 +203,10 @@
         final ui.Image image = frameInfo.image;
         final ByteData imageData = (await image.toByteData(format: ui.ImageByteFormat.png))!;
 
+        final String fileName = impellerEnabled ? 'impeller_2_dispose_op_restore_previous.apng.$i.png' : '2_dispose_op_restore_previous.apng.$i.png';
+
         final Uint8List goldenData = File(
-          path.join('flutter', 'lib', 'ui', 'fixtures', '2_dispose_op_restore_previous.apng.$i.png'),
+          path.join('flutter', 'lib', 'ui', 'fixtures', fileName),
         ).readAsBytesSync();
 
         expect(imageData.buffer.asUint8List(), goldenData);
diff --git a/testing/dart/color_filter_test.dart b/testing/dart/color_filter_test.dart
index e555360..66ec954 100644
--- a/testing/dart/color_filter_test.dart
+++ b/testing/dart/color_filter_test.dart
@@ -7,6 +7,8 @@
 
 import 'package:litetest/litetest.dart';
 
+import 'impeller_enabled.dart';
+
 const Color transparent = Color(0x00000000);
 const Color red = Color(0xFFAA0000);
 const Color green = Color(0xFF00AA00);
@@ -56,6 +58,11 @@
     Uint32List bytes = await getBytesForPaint(paint);
     expect(bytes[0], greenRedColorBlend);
 
+    // TODO(135699): enable this
+    if (impellerEnabled) {
+      return;
+    }
+
     paint.invertColors = true;
     bytes = await getBytesForPaint(paint);
     expect(bytes[0], greenRedColorBlendInverted);
@@ -87,6 +94,11 @@
     Uint32List bytes = await getBytesForPaint(paint);
     expect(bytes[0], greenGreyscaled);
 
+    // TODO(135699): enable this
+    if (impellerEnabled) {
+      return;
+    }
+
     paint.invertColors = true;
     bytes = await getBytesForPaint(paint);
     expect(bytes[0], greenInvertedGreyscaled);
@@ -118,6 +130,10 @@
     Uint32List bytes = await getBytesForPaint(paint);
     expect(bytes[0], greenLinearToSrgbGamma);
 
+    // TODO(135699): enable this
+    if (impellerEnabled) {
+      return;
+    }
     paint.invertColors = true;
     bytes = await getBytesForPaint(paint);
     expect(bytes[0], greenLinearToSrgbGammaInverted);
@@ -131,6 +147,11 @@
     Uint32List bytes = await getBytesForPaint(paint);
     expect(bytes[0], greenSrgbToLinearGamma);
 
+    // TODO(135699): enable this
+    if (impellerEnabled) {
+      return;
+    }
+
     paint.invertColors = true;
     bytes = await getBytesForPaint(paint);
     expect(bytes[0], greenSrgbToLinearGammaInverted);
diff --git a/testing/dart/encoding_test.dart b/testing/dart/encoding_test.dart
index 9ca46b9..2aa426a 100644
--- a/testing/dart/encoding_test.dart
+++ b/testing/dart/encoding_test.dart
@@ -10,6 +10,8 @@
 import 'package:litetest/litetest.dart';
 import 'package:path/path.dart' as path;
 
+import 'impeller_enabled.dart';
+
 const int _kWidth = 10;
 const int _kRadius = 2;
 
@@ -18,6 +20,10 @@
 
 void main() {
   test('decodeImageFromPixels float32', () async {
+    if (impellerEnabled) {
+      print('Disabled on Impeller - https://github.com/flutter/flutter/issues/135702');
+      return;
+    }
     const int width = 2;
     const int height = 2;
     final Float32List pixels = Float32List(width * height * 4);
@@ -91,6 +97,10 @@
   });
 
   test('Image.toByteData Unmodified format works with grayscale images', () async {
+    if (impellerEnabled) {
+      print('Disabled on Impeller - https://github.com/flutter/flutter/issues/135706');
+      return;
+    }
     final Image image = await GrayscaleImage.load();
     final ByteData data = (await image.toByteData(format: ImageByteFormat.rawUnmodified))!;
     final Uint8List bytes = data.buffer.asUint8List();
@@ -99,6 +109,10 @@
   });
 
   test('Image.toByteData PNG format works with simple image', () async {
+    if (impellerEnabled) {
+      print('Disabled on Impeller - https://github.com/flutter/flutter/issues/135706');
+      return;
+    }
     final Image image = await Square4x4Image.image;
     final ByteData data = (await image.toByteData(format: ImageByteFormat.png))!;
     final List<int> expected = await readFile('square.png');
diff --git a/testing/dart/fragment_shader_test.dart b/testing/dart/fragment_shader_test.dart
index e97eb51..da34695 100644
--- a/testing/dart/fragment_shader_test.dart
+++ b/testing/dart/fragment_shader_test.dart
@@ -12,6 +12,7 @@
 import 'package:litetest/litetest.dart';
 import 'package:path/path.dart' as path;
 
+import 'impeller_enabled.dart';
 import 'shader_test_file_utils.dart';
 
 void main() async {
@@ -172,6 +173,10 @@
   });
 
   test('FragmentShader simple shader renders correctly', () async {
+    if (impellerEnabled) {
+      print('Skipped for Impeller - https://github.com/flutter/flutter/issues/122823');
+      return;
+    }
     final FragmentProgram program = await FragmentProgram.fromAsset(
       'functions.frag.iplr',
     );
@@ -182,6 +187,10 @@
   });
 
   test('Reused FragmentShader simple shader renders correctly', () async {
+    if (impellerEnabled) {
+      print('Skipped for Impeller - https://github.com/flutter/flutter/issues/122823');
+      return;
+    }
     final FragmentProgram program = await FragmentProgram.fromAsset(
       'functions.frag.iplr',
     );
@@ -196,6 +205,10 @@
   });
 
   test('FragmentShader blue-green image renders green', () async {
+    if (impellerEnabled) {
+      print('Skipped for Impeller - https://github.com/flutter/flutter/issues/122823');
+      return;
+    }
     final FragmentProgram program = await FragmentProgram.fromAsset(
       'blue_green_sampler.frag.iplr',
     );
@@ -208,6 +221,10 @@
   });
 
   test('FragmentShader blue-green image renders green - GPU image', () async {
+    if (impellerEnabled) {
+      print('Skipped for Impeller - https://github.com/flutter/flutter/issues/122823');
+      return;
+    }
     final FragmentProgram program = await FragmentProgram.fromAsset(
       'blue_green_sampler.frag.iplr',
     );
@@ -220,6 +237,10 @@
   });
 
   test('FragmentShader with uniforms renders correctly', () async {
+    if (impellerEnabled) {
+      print('Skipped for Impeller - https://github.com/flutter/flutter/issues/122823');
+      return;
+    }
     final FragmentProgram program = await FragmentProgram.fromAsset(
       'uniforms.frag.iplr',
     );
@@ -246,6 +267,10 @@
   });
 
   test('FragmentShader shader with array uniforms renders correctly', () async {
+    if (impellerEnabled) {
+      print('Skipped for Impeller - https://github.com/flutter/flutter/issues/122823');
+      return;
+    }
     final FragmentProgram program = await FragmentProgram.fromAsset(
       'uniform_arrays.frag.iplr',
     );
@@ -260,6 +285,10 @@
   });
 
   test('FragmentShader The ink_sparkle shader is accepted', () async {
+    if (impellerEnabled) {
+      print('Skipped for Impeller - https://github.com/flutter/flutter/issues/122823');
+      return;
+    }
     final FragmentProgram program = await FragmentProgram.fromAsset(
       'ink_sparkle.frag.iplr',
     );
@@ -273,6 +302,10 @@
   });
 
   test('FragmentShader Uniforms are sorted correctly', () async {
+    if (impellerEnabled) {
+      print('Skipped for Impeller - https://github.com/flutter/flutter/issues/122823');
+      return;
+    }
     final FragmentProgram program = await FragmentProgram.fromAsset(
       'uniforms_sorted.frag.iplr',
     );
@@ -314,6 +347,10 @@
   });
 
   test('FragmentShader user defined functions do not redefine builtins', () async {
+    if (impellerEnabled) {
+      print('Skipped for Impeller - https://github.com/flutter/flutter/issues/122823');
+      return;
+    }
     final FragmentProgram program = await FragmentProgram.fromAsset(
       'no_builtin_redefinition.frag.iplr',
     );
@@ -324,6 +361,10 @@
   });
 
   test('FragmentShader fromAsset accepts a shader with no uniforms', () async {
+    if (impellerEnabled) {
+      print('Skipped for Impeller - https://github.com/flutter/flutter/issues/122823');
+      return;
+    }
     final FragmentProgram program = await FragmentProgram.fromAsset(
       'no_uniforms.frag.iplr',
     );
@@ -332,6 +373,11 @@
     shader.dispose();
   });
 
+  if (impellerEnabled) {
+    print('Skipped for Impeller - https://github.com/flutter/flutter/issues/122823');
+    return;
+  }
+
   // Test all supported GLSL ops. See lib/spirv/lib/src/constants.dart
   final Map<String, FragmentProgram> iplrSupportedGLSLOpShaders = await _loadShaderAssets(
     path.join('supported_glsl_op_shaders', 'iplr'),
diff --git a/testing/dart/gpu_test.dart b/testing/dart/gpu_test.dart
index 0c96b66..9d65c8e 100644
--- a/testing/dart/gpu_test.dart
+++ b/testing/dart/gpu_test.dart
@@ -7,6 +7,8 @@
 import 'package:litetest/litetest.dart';
 import '../../lib/gpu/lib/gpu.dart' as gpu;
 
+import 'impeller_enabled.dart';
+
 void main() {
   // TODO(131346): Remove this once we migrate the Dart GPU API into this space.
   test('smoketest', () async {
@@ -25,9 +27,14 @@
   test('gpu.context throws exception for incompatible embedders', () async {
     try {
       // ignore: unnecessary_statements
-      gpu.gpuContext; // Force the
-      fail('Exception not thrown');
+      gpu.gpuContext; // Force the context to instantiate.
+      if (!impellerEnabled) {
+        fail('Exception not thrown, but no Impeller context available.');
+      }
     } catch (e) {
+      if (impellerEnabled) {
+        fail('Exception thrown even though Impeller is enabled.');
+      }
       expect(
           e.toString(),
           contains(
diff --git a/testing/dart/image_filter_test.dart b/testing/dart/image_filter_test.dart
index 0fa8651..0a79513 100644
--- a/testing/dart/image_filter_test.dart
+++ b/testing/dart/image_filter_test.dart
@@ -7,6 +7,8 @@
 
 import 'package:litetest/litetest.dart';
 
+import 'impeller_enabled.dart';
+
 const Color red = Color(0xFFAA0000);
 const Color green = Color(0xFF00AA00);
 
@@ -174,6 +176,10 @@
   }
 
   test('ImageFilter - blur', () async {
+    if (impellerEnabled) {
+      print('Disabled - see https://github.com/flutter/flutter/issues/135712');
+      return;
+    }
     final Paint paint = Paint()
       ..color = green
       ..imageFilter = makeBlur(1.0, 1.0, TileMode.decal);
@@ -201,6 +207,11 @@
   });
 
   test('ImageFilter - matrix', () async {
+    if (impellerEnabled) {
+      print('Disabled - see https://github.com/flutter/flutter/issues/135712');
+      return;
+    }
+
     final Paint paint = Paint()
       ..color = green
       ..imageFilter = makeScale(2.0, 2.0, 1.5, 1.5);
@@ -227,6 +238,11 @@
   });
 
   test('ImageFilter - from color filters', () async {
+    if (impellerEnabled) {
+      print('Disabled - see https://github.com/flutter/flutter/issues/135712');
+      return;
+    }
+
     final Paint paint = Paint()
       ..color = green
       ..imageFilter = const ColorFilter.matrix(constValueColorMatrix);
@@ -236,6 +252,11 @@
   });
 
   test('ImageFilter - color filter composition', () async {
+    if (impellerEnabled) {
+      print('Disabled - see https://github.com/flutter/flutter/issues/135712');
+      return;
+    }
+
     final ImageFilter compOrder1 = ImageFilter.compose(
       outer: const ColorFilter.matrix(halvesBrightnessColorMatrix),
       inner: const ColorFilter.matrix(constValueColorMatrix),
diff --git a/testing/dart/impeller_enabled.dart b/testing/dart/impeller_enabled.dart
new file mode 100644
index 0000000..18c457e
--- /dev/null
+++ b/testing/dart/impeller_enabled.dart
@@ -0,0 +1,7 @@
+// 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.
+
+import 'dart:io';
+
+bool get impellerEnabled => Platform.executableArguments.contains('--enable-impeller');
diff --git a/testing/dart/observatory/skp_test.dart b/testing/dart/observatory/skp_test.dart
index 239e3b5..6c2a146 100644
--- a/testing/dart/observatory/skp_test.dart
+++ b/testing/dart/observatory/skp_test.dart
@@ -12,6 +12,8 @@
 import 'package:vm_service/vm_service.dart' as vms;
 import 'package:vm_service/vm_service_io.dart';
 
+import '../impeller_enabled.dart';
+
 void main() {
   test('Capture an SKP ', () async {
     final developer.ServiceProtocolInfo info = await developer.Service.getInfo();
@@ -42,13 +44,19 @@
     PlatformDispatcher.instance.scheduleFrame();
     await completer.future;
 
-    final vms.Response response = await vmService.callServiceExtension('_flutter.screenshotSkp');
+    try {
+      final vms.Response response = await vmService.callServiceExtension('_flutter.screenshotSkp');
+      expect(impellerEnabled, false);
+      final String base64data = response.json!['skp'] as String;
+      expect(base64data, isNotNull);
+      expect(base64data, isNotEmpty);
+      final Uint8List decoded = base64Decode(base64data);
+      expect(decoded.sublist(0, 8), 'skiapict'.codeUnits);
+    } on vms.RPCError catch (e) {
+      expect(impellerEnabled, true);
+      expect(e.toString(), contains('Cannot capture SKP screenshot with Impeller enabled.'));
+    }
 
-    final String base64data = response.json!['skp'] as String;
-    expect(base64data, isNotNull);
-    expect(base64data, isNotEmpty);
-    final Uint8List decoded = base64Decode(base64data);
-    expect(decoded.sublist(0, 8), 'skiapict'.codeUnits);
 
     await vmService.dispose();
   });
diff --git a/testing/dart/observatory/tracing_test.dart b/testing/dart/observatory/tracing_test.dart
index 1937c9b..6372a6a 100644
--- a/testing/dart/observatory/tracing_test.dart
+++ b/testing/dart/observatory/tracing_test.dart
@@ -12,6 +12,8 @@
 import 'package:vm_service/vm_service_io.dart';
 import 'package:vm_service_protos/vm_service_protos.dart';
 
+import '../impeller_enabled.dart';
+
 Future<void> _testChromeFormatTrace(vms.VmService vmService) async {
   final vms.Timeline timeline = await vmService.getVMTimeline();
 
@@ -32,7 +34,7 @@
     }
   }
   expect(saveLayerRecordCount, 3);
-  expect(saveLayerCount, 3);
+  expect(saveLayerCount, impellerEnabled ? 2 : 3);
   expect(flowEventCount, 5);
 }
 
@@ -59,7 +61,7 @@
     }
   }
   expect(saveLayerRecordCount, 3);
-  expect(saveLayerCount, 3);
+  expect(saveLayerCount, impellerEnabled ? 2 : 3);
   expect(flowIdCount, 5);
 }
 
@@ -80,6 +82,7 @@
       final PictureRecorder recorder = PictureRecorder();
       final Canvas canvas = Canvas(recorder);
       canvas.drawColor(const Color(0xff0000ff), BlendMode.srcOut);
+      // Will saveLayer implicitly for Skia, but not Impeller.
       canvas.drawPaint(Paint()..imageFilter = ImageFilter.blur(sigmaX: 2, sigmaY: 3));
       canvas.saveLayer(null, Paint());
       canvas.drawRect(const Rect.fromLTRB(10, 10, 20, 20), Paint());
diff --git a/testing/dart/observatory/vmservice_methods_test.dart b/testing/dart/observatory/vmservice_methods_test.dart
index fa522b7..14fd20d 100644
--- a/testing/dart/observatory/vmservice_methods_test.dart
+++ b/testing/dart/observatory/vmservice_methods_test.dart
@@ -12,6 +12,8 @@
 import 'package:vm_service/vm_service.dart' as vms;
 import 'package:vm_service/vm_service_io.dart';
 
+import '../impeller_enabled.dart';
+
 void main() {
   test('Setting invalid directory returns an error', () async {
     vms.VmService? vmService;
@@ -59,7 +61,7 @@
         'ext.ui.window.impellerEnabled',
         isolateId: isolateId,
       );
-      expect(response.json!['enabled'], false);
+      expect(response.json!['enabled'], impellerEnabled);
     } finally {
       await vmService?.dispose();
     }
diff --git a/testing/run_tests.py b/testing/run_tests.py
index 898201c..500dbba 100755
--- a/testing/run_tests.py
+++ b/testing/run_tests.py
@@ -576,13 +576,37 @@
     )
 
 
-def gather_dart_test(
-    build_dir,
-    dart_file,
-    multithreaded,
-    enable_observatory=False,
-    expect_failure=False,
-):
+class FlutterTesterOptions():
+
+  def __init__(
+      self,
+      multithreaded=False,
+      enable_impeller=False,
+      enable_observatory=False,
+      expect_failure=False
+  ):
+    self.multithreaded = multithreaded
+    self.enable_impeller = enable_impeller
+    self.enable_observatory = enable_observatory
+    self.expect_failure = expect_failure
+
+  def apply_args(self, command_args):
+    if not self.enable_observatory:
+      command_args.append('--disable-observatory')
+
+    if self.enable_impeller:
+      command_args += ['--enable-impeller']
+
+    if self.multithreaded:
+      command_args.insert(0, '--force-multithreading')
+
+  def threading_description(self):
+    if self.multithreaded:
+      return 'multithreaded'
+    return 'single-threaded'
+
+
+def gather_dart_test(build_dir, dart_file, options):
   kernel_file_name = os.path.basename(dart_file) + '.dill'
   kernel_file_output = os.path.join(build_dir, 'gen', kernel_file_name)
   error_message = "%s doesn't exist. Please run the build that populates %s" % (
@@ -591,8 +615,8 @@
   assert os.path.isfile(kernel_file_output), error_message
 
   command_args = []
-  if not enable_observatory:
-    command_args.append('--disable-observatory')
+
+  options.apply_args(command_args)
 
   dart_file_contents = open(dart_file, 'r')
   custom_options = re.findall(
@@ -610,18 +634,12 @@
       kernel_file_output,
   ]
 
-  if multithreaded:
-    threading = 'multithreaded'
-    command_args.insert(0, '--force-multithreading')
-  else:
-    threading = 'single-threaded'
-
   tester_name = 'flutter_tester'
   logger.info(
       "Running test '%s' using '%s' (%s)", kernel_file_name, tester_name,
-      threading
+      options.threading_description()
   )
-  forbidden_output = [] if 'unopt' in build_dir or expect_failure else [
+  forbidden_output = [] if 'unopt' in build_dir or options.expect_failure else [
       '[ERROR'
   ]
   return EngineExecutableTask(
@@ -630,7 +648,7 @@
       None,
       command_args,
       forbidden_output=forbidden_output,
-      expect_failure=expect_failure,
+      expect_failure=options.expect_failure,
   )
 
 
@@ -849,8 +867,38 @@
         logger.info(
             "Gathering dart test '%s' with observatory enabled", dart_test_file
         )
-        yield gather_dart_test(build_dir, dart_test_file, True, True)
-        yield gather_dart_test(build_dir, dart_test_file, False, True)
+        yield gather_dart_test(
+            build_dir, dart_test_file,
+            FlutterTesterOptions(
+                multithreaded=True,
+                enable_impeller=False,
+                enable_observatory=True
+            )
+        )
+        yield gather_dart_test(
+            build_dir, dart_test_file,
+            FlutterTesterOptions(
+                multithreaded=True,
+                enable_impeller=True,
+                enable_observatory=True
+            )
+        )
+        yield gather_dart_test(
+            build_dir, dart_test_file,
+            FlutterTesterOptions(
+                multithreaded=False,
+                enable_impeller=False,
+                enable_observatory=True
+            )
+        )
+        yield gather_dart_test(
+            build_dir, dart_test_file,
+            FlutterTesterOptions(
+                multithreaded=False,
+                enable_impeller=True,
+                enable_observatory=True
+            )
+        )
 
   for dart_test_file in dart_tests:
     if test_filter is not None and os.path.basename(dart_test_file
@@ -858,8 +906,22 @@
       logger.info("Skipping '%s' due to filter.", dart_test_file)
     else:
       logger.info("Gathering dart test '%s'", dart_test_file)
-      yield gather_dart_test(build_dir, dart_test_file, True)
-      yield gather_dart_test(build_dir, dart_test_file, False)
+      yield gather_dart_test(
+          build_dir, dart_test_file,
+          FlutterTesterOptions(multithreaded=True, enable_impeller=False)
+      )
+      yield gather_dart_test(
+          build_dir, dart_test_file,
+          FlutterTesterOptions(multithreaded=True, enable_impeller=True)
+      )
+      yield gather_dart_test(
+          build_dir, dart_test_file,
+          FlutterTesterOptions(multithreaded=False, enable_impeller=False)
+      )
+      yield gather_dart_test(
+          build_dir, dart_test_file,
+          FlutterTesterOptions(multithreaded=False, enable_impeller=True)
+      )
 
 
 def gather_dart_smoke_test(build_dir, test_filter):
@@ -874,8 +936,14 @@
                                                  ) not in test_filter:
     logger.info("Skipping '%s' due to filter.", smoke_test)
   else:
-    yield gather_dart_test(build_dir, smoke_test, True, expect_failure=True)
-    yield gather_dart_test(build_dir, smoke_test, False, expect_failure=True)
+    yield gather_dart_test(
+        build_dir, smoke_test,
+        FlutterTesterOptions(multithreaded=True, expect_failure=True)
+    )
+    yield gather_dart_test(
+        build_dir, smoke_test,
+        FlutterTesterOptions(multithreaded=False, expect_failure=True)
+    )
 
 
 def gather_dart_package_tests(build_dir, package_path, extra_opts):
diff --git a/testing/test_vulkan_context.cc b/testing/test_vulkan_context.cc
index ea36581..5652431 100644
--- a/testing/test_vulkan_context.cc
+++ b/testing/test_vulkan_context.cc
@@ -14,19 +14,12 @@
 
 #include "flutter/fml/memory/ref_ptr.h"
 #include "flutter/fml/native_library.h"
+#include "flutter/vulkan/swiftshader_path.h"
 #include "third_party/skia/include/core/SkSurface.h"
 #include "third_party/skia/include/gpu/GrDirectContext.h"
 #include "third_party/skia/include/gpu/vk/GrVkExtensions.h"
 #include "vulkan/vulkan_core.h"
 
-#ifdef FML_OS_MACOSX
-#define VULKAN_SO_PATH "libvk_swiftshader.dylib"
-#elif FML_OS_WIN
-#define VULKAN_SO_PATH "vk_swiftshader.dll"
-#else
-#define VULKAN_SO_PATH "libvk_swiftshader.so"
-#endif
-
 namespace flutter {
 namespace testing {
 
diff --git a/vulkan/swiftshader_path.h b/vulkan/swiftshader_path.h
new file mode 100644
index 0000000..424116a
--- /dev/null
+++ b/vulkan/swiftshader_path.h
@@ -0,0 +1,18 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FLUTTER_VULKAN_SWIFTSHADER_PATH_H_
+#define FLUTTER_VULKAN_SWIFTSHADER_PATH_H_
+
+#ifndef VULKAN_SO_PATH
+#if FML_OS_MACOSX
+#define VULKAN_SO_PATH "libvk_swiftshader.dylib"
+#elif FML_OS_WIN
+#define VULKAN_SO_PATH "vk_swiftshader.dll"
+#else
+#define VULKAN_SO_PATH "libvk_swiftshader.so"
+#endif  // !FML_OS_MACOSX && !FML_OS_WIN
+#endif  // VULKAN_SO_PATH
+
+#endif  // FLUTTER_VULKAN_SWIFTSHADER_PATH_H_
diff --git a/vulkan/vulkan_window.cc b/vulkan/vulkan_window.cc
index f7f51dc..345919b 100644
--- a/vulkan/vulkan_window.cc
+++ b/vulkan/vulkan_window.cc
@@ -118,6 +118,7 @@
 }
 
 bool VulkanWindow::CreateSkiaGrContext() {
+#ifdef SK_VUKLAN
   GrVkBackendContext backend_context;
 
   if (!CreateSkiaBackendContext(&backend_context)) {
@@ -138,6 +139,9 @@
   skia_gr_context_ = context;
 
   return true;
+#else
+  return false;
+#endif  // SK_VULKAN
 }
 
 bool VulkanWindow::CreateSkiaBackendContext(GrVkBackendContext* context) {