Flutter Engine
The Flutter Engine
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"
21
22#define GLFW_INCLUDE_NONE
23#include "third_party/glfw/include/GLFW/glfw3.h"
24
25namespace impeller {
26
27namespace {
28std::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;
33 playground_switches);
34}
35
36// Returns a static instance to a playground that can be used across tests.
37const 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.
67static 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
75namespace {
76std::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
89std::string GetGoldenFilename() {
90 return GetTestName() + ".png";
91}
92
93bool 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();
101 test_name, filename, screenshot->GetWidth(), screenshot->GetHeight());
102 if (!screenshot->WriteToPNG(
104 FML_LOG(ERROR) << "Failed to write screenshot to " << filename;
105 return false;
106 }
107 return true;
108}
109
110} // namespace
111
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
120 : typographer_context_(TypographerContextSkia::Make()),
122
124
126 std::shared_ptr<TypographerContext> typographer_context) {
127 typographer_context_ = std::move(typographer_context);
128};
129
131 ASSERT_FALSE(dlopen("/usr/local/lib/libMoltenVK.dylib", RTLD_NOLOAD));
132}
133
134namespace {
135bool DoesSupportWideGamutTests() {
136#ifdef __arm64__
137 return true;
138#else
139 return false;
140#endif
141}
142} // namespace
143
145 std::filesystem::path testing_assets_path =
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()) {
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;
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 }
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
198 "gpu_string", GetContext()->DescribeGpuModel());
199}
200
202 return GetParam();
203}
204
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
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);
224
225 screenshot = pimpl_->screenshotter->MakeScreenshot(renderer, picture,
226 pimpl_->window_size);
227 }
228
229 return SaveScreenshot(std::move(screenshot));
230}
231
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) {
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
252 const sk_sp<flutter::DisplayList>& list) {
253 DlDispatcher dispatcher;
254 list->Dispatch(dispatcher);
256 return OpenPlaygroundHere(std::move(picture));
257}
258
260 bool* p_open,
261 ImGuiWindowFlags flags) {
262 return false;
263}
264
266 const char* fixture_name,
267 bool enable_mipmapping) const {
268 std::shared_ptr<fml::Mapping> mapping =
271 enable_mipmapping);
272 if (result) {
273 result->SetLabel(fixture_name);
274 }
275 return result;
276}
277
279 const char* fixture_name,
280 bool enable_mipmapping) const {
281 std::shared_ptr<Texture> texture =
282 CreateTextureForFixture(fixture_name, enable_mipmapping);
284}
285
287 const char* asset_name) const {
288 const std::shared_ptr<fml::Mapping> fixture =
290 if (!fixture || fixture->GetSize() == 0) {
291 return {};
292 }
293 return RuntimeStage::DecodeRuntimeStages(fixture);
294}
295
296std::shared_ptr<Context> GoldenPlaygroundTest::GetContext() const {
297 return pimpl_->screenshotter->GetPlayground().GetContext();
298}
299
300std::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
320 return pimpl_->screenshotter->GetPlayground().GetContentScale();
321}
322
324 return 0.0f;
325}
326
328 return pimpl_->window_size;
329}
330
331void GoldenPlaygroundTest::GoldenPlaygroundTest::SetWindowSize(ISize size) {
332 pimpl_->window_size = size;
333}
334
336 const std::shared_ptr<Capabilities>& capabilities) {
337 return pimpl_->screenshotter->GetPlayground().SetCapabilities(capabilities);
338}
339
340} // namespace impeller
int find(T *array, int N, T item)
void Dispatch(DlOpReceiver &ctx) const
Wraps a closure that is invoked in the destructor unless released by the caller.
Definition: closure.h:32
static sk_sp< DlImageImpeller > Make(std::shared_ptr< Texture > texture, OwningContext owning_context=OwningContext::kIO)
std::function< std::optional< Picture >(AiksContext &renderer)> AiksPlaygroundCallback
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
RuntimeStage::Map OpenAssetAsRuntimeStage(const char *asset_name) const
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::unique_ptr< PlaygroundImpl > Create(PlaygroundBackend backend, PlaygroundSwitches switches)
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
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()
FlutterSemanticsFlag flags
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback
GAsyncResult * result
#define GLFW_TRUE
Definition: flutter_glfw.cc:33
#define FML_LOG(severity)
Definition: logging.h:82
#define FML_CHECK(condition)
Definition: logging.h:85
#define IMP_AIKSTEST(name)
FlTexture * texture
SK_API sk_sp< SkDocument > Make(SkWStream *dst, const SkSerialProcs *=nullptr, std::function< void(const SkPicture *)> onEndPage=nullptr)
sk_sp< const SkPicture > picture
Definition: SkRecords.h:299
static void Shutdown(Dart_NativeArguments args)
const char * GetTestingAssetsPath()
Returns the directory containing assets shared across all tests.
std::unique_ptr< fml::Mapping > OpenFixtureAsMapping(const std::string &fixture_name)
Opens a fixture of the given file name and returns a mapping to its contents.
Definition: testing.cc:59
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition: switches.h:57
DEF_SWITCHES_START aot vmservice shared library name
Definition: switches.h:32
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
static const std::vector< std::string > kSkipTests
float Scalar
Definition: scalar.h:18
PlaygroundBackend
Definition: playground.h:27
#define ERROR(message)
Definition: elf_loader.cc:260