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