Flutter Impeller
golden_playground_test_mac.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <dlfcn.h>
6 #include <filesystem>
7 #include <memory>
8 
9 #include "display_list/display_list.h"
11 
15 #include "flutter/third_party/abseil-cpp/absl/base/no_destructor.h"
16 #include "fml/closure.h"
22 
23 #define GLFW_INCLUDE_NONE
24 #include "third_party/glfw/include/GLFW/glfw3.h"
25 
26 namespace impeller {
27 
28 namespace {
29 std::unique_ptr<PlaygroundImpl> MakeVulkanPlayground(bool enable_validations) {
30  FML_CHECK(::glfwInit() == GLFW_TRUE);
31  PlaygroundSwitches playground_switches;
32  playground_switches.enable_vulkan_validation = enable_validations;
34  playground_switches);
35 }
36 
37 // Returns a static instance to a playground that can be used across tests.
38 const std::unique_ptr<PlaygroundImpl>& GetSharedVulkanPlayground(
39  bool enable_validations) {
40  if (enable_validations) {
41  static absl::NoDestructor<std::unique_ptr<PlaygroundImpl>>
42  vulkan_validation_playground(
43  MakeVulkanPlayground(/*enable_validations=*/true));
44  // TODO(142237): This can be removed when the thread local storage is
45  // removed.
46  static fml::ScopedCleanupClosure context_cleanup(
47  [&] { (*vulkan_validation_playground)->GetContext()->Shutdown(); });
48  return *vulkan_validation_playground;
49  } else {
50  static absl::NoDestructor<std::unique_ptr<PlaygroundImpl>>
51  vulkan_playground(MakeVulkanPlayground(/*enable_validations=*/false));
52  // TODO(142237): This can be removed when the thread local storage is
53  // removed.
54  static fml::ScopedCleanupClosure context_cleanup(
55  [&] { (*vulkan_playground)->GetContext()->Shutdown(); });
56  return *vulkan_playground;
57  }
58 }
59 
60 } // namespace
61 
62 #define IMP_AIKSTEST(name) \
63  "impeller_Play_AiksTest_" #name "_Metal", \
64  "impeller_Play_AiksTest_" #name "_OpenGLES", \
65  "impeller_Play_AiksTest_" #name "_Vulkan"
66 
67 // If you add a new playground test to the aiks unittests and you do not want it
68 // to also be a golden test, then add the test name here.
69 static const std::vector<std::string> kSkipTests = {
70  // TextRotated is flakey and we can't seem to get it to stabilize on Skia
71  // Gold.
72  IMP_AIKSTEST(TextRotated),
73  // Runtime stage based tests get confused with a Metal context.
74  "impeller_Play_AiksTest_CanRenderClippedRuntimeEffects_Vulkan",
75 };
76 
77 namespace {
78 std::string GetTestName() {
79  std::string suite_name =
80  ::testing::UnitTest::GetInstance()->current_test_suite()->name();
81  std::string test_name =
82  ::testing::UnitTest::GetInstance()->current_test_info()->name();
83  std::stringstream ss;
84  ss << "impeller_" << suite_name << "_" << test_name;
85  std::string result = ss.str();
86  // Make sure there are no slashes in the test name.
87  std::replace(result.begin(), result.end(), '/', '_');
88  return result;
89 }
90 
91 std::string GetGoldenFilename(const std::string& postfix) {
92  return GetTestName() + postfix + ".png";
93 }
94 } // namespace
95 
97  std::unique_ptr<testing::Screenshot> screenshot,
98  const std::string& postfix) {
99  if (!screenshot || !screenshot->GetBytes()) {
100  FML_LOG(ERROR) << "Failed to collect screenshot for test " << GetTestName();
101  return false;
102  }
103  std::string test_name = GetTestName();
104  std::string filename = GetGoldenFilename(postfix);
106  test_name, filename, screenshot->GetWidth(), screenshot->GetHeight());
107  if (!screenshot->WriteToPNG(
109  FML_LOG(ERROR) << "Failed to write screenshot to " << filename;
110  return false;
111  }
112  return true;
113 }
114 
116  std::unique_ptr<PlaygroundImpl> test_vulkan_playground;
117  std::unique_ptr<PlaygroundImpl> test_opengl_playground;
118  std::unique_ptr<testing::Screenshotter> screenshotter;
119  ISize window_size = ISize{1024, 768};
120 };
121 
123  : typographer_context_(TypographerContextSkia::Make()),
125 
127 
129  std::shared_ptr<TypographerContext> typographer_context) {
130  typographer_context_ = std::move(typographer_context);
131 };
132 
134  ASSERT_FALSE(dlopen("/usr/local/lib/libMoltenVK.dylib", RTLD_NOLOAD));
135 
136  auto context = GetContext();
137  if (context) {
138  context->DisposeThreadLocalCachedResources();
139  }
140 }
141 
142 namespace {
143 bool DoesSupportWideGamutTests() {
144 #ifdef __arm64__
145  return true;
146 #else
147  return false;
148 #endif
149 }
150 } // namespace
151 
153  std::filesystem::path testing_assets_path =
154  flutter::testing::GetTestingAssetsPath();
155  std::filesystem::path target_path = testing_assets_path.parent_path()
156  .parent_path()
157  .parent_path()
158  .parent_path();
159  std::filesystem::path icd_path = target_path / "vk_swiftshader_icd.json";
160  setenv("VK_ICD_FILENAMES", icd_path.c_str(), 1);
161 
162  std::string test_name = GetTestName();
163  PlaygroundSwitches switches;
164  switches.enable_wide_gamut =
165  test_name.find("WideGamut_") != std::string::npos;
166  switches.flags.antialiased_lines =
167  test_name.find("ExperimentAntialiasLines_") != std::string::npos;
168  switch (GetParam()) {
170  if (!DoesSupportWideGamutTests()) {
171  GTEST_SKIP()
172  << "This metal device doesn't support wide gamut golden tests.";
173  }
174  pimpl_->screenshotter =
175  std::make_unique<testing::MetalScreenshotter>(switches);
176  break;
178  if (switches.enable_wide_gamut) {
179  GTEST_SKIP() << "Vulkan doesn't support wide gamut golden tests.";
180  }
181  if (switches.flags.antialiased_lines) {
182  GTEST_SKIP()
183  << "Vulkan doesn't support antialiased lines golden tests.";
184  }
185  const std::unique_ptr<PlaygroundImpl>& playground =
186  GetSharedVulkanPlayground(/*enable_validations=*/true);
187  pimpl_->screenshotter =
188  std::make_unique<testing::VulkanScreenshotter>(playground);
189  break;
190  }
192  if (switches.enable_wide_gamut) {
193  GTEST_SKIP() << "OpenGLES doesn't support wide gamut golden tests.";
194  }
195  if (switches.flags.antialiased_lines) {
196  GTEST_SKIP()
197  << "OpenGLES doesn't support antialiased lines golden tests.";
198  }
199  FML_CHECK(::glfwInit() == GLFW_TRUE);
200  PlaygroundSwitches playground_switches;
201  playground_switches.use_angle = true;
202  pimpl_->test_opengl_playground = PlaygroundImpl::Create(
203  PlaygroundBackend::kOpenGLES, playground_switches);
204  pimpl_->screenshotter = std::make_unique<testing::VulkanScreenshotter>(
205  pimpl_->test_opengl_playground);
206  break;
207  }
208  }
209 
210  if (std::find(kSkipTests.begin(), kSkipTests.end(), test_name) !=
211  kSkipTests.end()) {
212  GTEST_SKIP()
213  << "GoldenPlaygroundTest doesn't support interactive playground tests "
214  "yet.";
215  }
216 
218  "gpu_string", GetContext()->DescribeGpuModel());
219 }
220 
222  return GetParam();
223 }
224 
226  const AiksDlPlaygroundCallback& callback) {
227  AiksContext renderer(GetContext(), typographer_context_);
228 
229  std::unique_ptr<testing::Screenshot> screenshot;
230  Point content_scale =
231  pimpl_->screenshotter->GetPlayground().GetContentScale();
232 
233  ISize physical_window_size(
234  std::round(pimpl_->window_size.width * content_scale.x),
235  std::round(pimpl_->window_size.height * content_scale.y));
236  for (int i = 0; i < 2; ++i) {
237  auto display_list = callback();
238  auto texture =
239  DisplayListToTexture(display_list, physical_window_size, renderer);
240  screenshot = pimpl_->screenshotter->MakeScreenshot(renderer, texture);
241  }
242  return SaveScreenshot(std::move(screenshot));
243 }
244 
246  const sk_sp<flutter::DisplayList>& list) {
247  return OpenPlaygroundHere([&list]() { return list; });
248 }
249 
250 bool GoldenPlaygroundTest::ImGuiBegin(const char* name,
251  bool* p_open,
252  ImGuiWindowFlags flags) {
253  return false;
254 }
255 
257  const char* fixture_name,
258  bool enable_mipmapping) const {
259  std::shared_ptr<fml::Mapping> mapping =
260  flutter::testing::OpenFixtureAsMapping(fixture_name);
261  auto result = Playground::CreateTextureForMapping(GetContext(), mapping,
262  enable_mipmapping);
263  if (result) {
264  result->SetLabel(fixture_name);
265  }
266  return result;
267 }
268 
270  const char* fixture_name,
271  bool enable_mipmapping) const {
272  std::shared_ptr<Texture> texture =
273  CreateTextureForFixture(fixture_name, enable_mipmapping);
274  return DlImageImpeller::Make(texture);
275 }
276 
278  const char* asset_name) const {
279  const std::shared_ptr<fml::Mapping> fixture =
280  flutter::testing::OpenFixtureAsMapping(asset_name);
281  if (!fixture || fixture->GetSize() == 0) {
282  return {};
283  }
284  return RuntimeStage::DecodeRuntimeStages(fixture);
285 }
286 
287 std::shared_ptr<Context> GoldenPlaygroundTest::GetContext() const {
288  if (!pimpl_->screenshotter) {
289  return nullptr;
290  }
291  return pimpl_->screenshotter->GetPlayground().GetContext();
292 }
293 
294 std::shared_ptr<Context> GoldenPlaygroundTest::MakeContext() const {
295  if (GetParam() == PlaygroundBackend::kMetal) {
296  /// On Metal we create a context for each test.
297  return GetContext();
298  } else if (GetParam() == PlaygroundBackend::kVulkan) {
299  bool enable_vulkan_validations = true;
300  FML_CHECK(!pimpl_->test_vulkan_playground)
301  << "We don't support creating multiple contexts for one test";
302  pimpl_->test_vulkan_playground =
303  MakeVulkanPlayground(enable_vulkan_validations);
304  pimpl_->screenshotter = std::make_unique<testing::VulkanScreenshotter>(
305  pimpl_->test_vulkan_playground);
306  return pimpl_->test_vulkan_playground->GetContext();
307  } else {
308  /// On OpenGL we create a context for each test.
309  return GetContext();
310  }
311 }
312 
314  return pimpl_->screenshotter->GetPlayground().GetContentScale();
315 }
316 
318  return 0.0f;
319 }
320 
322  return pimpl_->window_size;
323 }
324 
325 void GoldenPlaygroundTest::GoldenPlaygroundTest::SetWindowSize(ISize size) {
326  pimpl_->window_size = size;
327 }
328 
330  const std::shared_ptr<Capabilities>& capabilities) {
331  return pimpl_->screenshotter->GetPlayground().SetCapabilities(capabilities);
332 }
333 
334 std::unique_ptr<testing::Screenshot> GoldenPlaygroundTest::MakeScreenshot(
335  const sk_sp<flutter::DisplayList>& list) {
336  AiksContext renderer(GetContext(), typographer_context_);
337  Point content_scale =
338  pimpl_->screenshotter->GetPlayground().GetContentScale();
339 
340  ISize physical_window_size(
341  std::round(pimpl_->window_size.width * content_scale.x),
342  std::round(pimpl_->window_size.height * content_scale.y));
343  return pimpl_->screenshotter->MakeScreenshot(
344  renderer, DisplayListToTexture(list, physical_window_size, renderer));
345 }
346 
347 } // namespace impeller
static sk_sp< DlImageImpeller > Make(std::shared_ptr< Texture > texture, OwningContext owning_context=OwningContext::kIO)
sk_sp< flutter::DlImage > CreateDlImageForFixture(const char *fixture_name, bool enable_mipmapping=false) const
fml::Status SetCapabilities(const std::shared_ptr< Capabilities > &capabilities)
void SetTypographerContext(std::shared_ptr< TypographerContext > typographer_context)
std::shared_ptr< Context > MakeContext() const
static bool SaveScreenshot(std::unique_ptr< testing::Screenshot > screenshot, const std::string &postfix="")
std::unique_ptr< testing::Screenshot > MakeScreenshot(const sk_sp< flutter::DisplayList > &list)
RuntimeStage::Map OpenAssetAsRuntimeStage(const char *asset_name) const
bool OpenPlaygroundHere(Picture picture)
static bool ImGuiBegin(const char *name, bool *p_open, ImGuiWindowFlags flags)
std::function< sk_sp< flutter::DisplayList >()> AiksDlPlaygroundCallback
std::shared_ptr< Context > GetContext() const
std::shared_ptr< Texture > CreateTextureForFixture(const char *fixture_name, bool enable_mipmapping=false) const
static std::shared_ptr< Texture > CreateTextureForMapping(const std::shared_ptr< Context > &context, std::shared_ptr< fml::Mapping > mapping, bool enable_mipmapping=false)
Definition: playground.cc:433
static std::unique_ptr< PlaygroundImpl > Create(PlaygroundBackend backend, PlaygroundSwitches switches)
std::map< RuntimeStageBackend, std::shared_ptr< RuntimeStage > > Map
Definition: runtime_stage.h:24
static Map DecodeRuntimeStages(const std::shared_ptr< fml::Mapping > &payload)
static GoldenDigest * Instance()
void AddDimension(const std::string &name, const std::string &value)
void AddImage(const std::string &test_name, const std::string &filename, int32_t width, int32_t height)
std::string GetFilenamePath(const std::string &filename) const
static WorkingDirectory * Instance()
#define IMP_AIKSTEST(name)
std::shared_ptr< Texture > DisplayListToTexture(const sk_sp< flutter::DisplayList > &display_list, ISize size, AiksContext &context, bool reset_host_buffer, bool generate_mips)
Render the provided display list to a texture with the given size.
static const std::vector< std::string > kSkipTests
float Scalar
Definition: scalar.h:19
PlaygroundBackend
Definition: playground.h:27
bool antialiased_lines
When turned on DrawLine will use the experimental antialiased path.
Definition: flags.h:14