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 <memory>
6
7#include "flutter/assets/directory_asset_bundle.h"
8#include "flutter/flow/layers/container_layer.h"
9#include "flutter/flow/layers/layer.h"
10#include "flutter/flow/layers/physical_shape_layer.h"
11#include "flutter/flow/layers/picture_layer.h"
12#include "flutter/fml/command_line.h"
13#include "flutter/fml/file.h"
14#include "flutter/fml/log_settings.h"
15#include "flutter/fml/unique_fd.h"
16#include "flutter/shell/common/persistent_cache.h"
17#include "flutter/shell/common/shell_test.h"
18#include "flutter/shell/common/switches.h"
19#include "flutter/shell/version/version.h"
20#include "flutter/testing/testing.h"
21#include "include/core/SkPicture.h"
22
23namespace flutter {
24namespace testing {
25
26static void WaitForIO(Shell* shell) {
27 std::promise<bool> io_task_finished;
28 shell->GetTaskRunners().GetIOTaskRunner()->PostTask(
29 [&io_task_finished]() { io_task_finished.set_value(true); });
30 io_task_finished.get_future().wait();
31}
32
33TEST_F(ShellTest, CacheSkSLWorks) {
34 // Create a temp dir to store the persistent cache
35 fml::ScopedTemporaryDirectory dir;
36 PersistentCache::SetCacheDirectoryPath(dir.path());
37 PersistentCache::ResetCacheForProcess();
38
39 auto settings = CreateSettingsForFixture();
40 settings.cache_sksl = true;
41 settings.dump_skp_on_shader_compilation = true;
42
43 fml::AutoResetWaitableEvent firstFrameLatch;
44 settings.frame_rasterized_callback =
45 [&firstFrameLatch](const FrameTiming& t) { firstFrameLatch.Signal(); };
46
47 auto sksl_config = RunConfiguration::InferFromSettings(settings);
48 sksl_config.SetEntrypoint("emptyMain");
49 std::unique_ptr<Shell> shell = CreateShell(settings);
50 PlatformViewNotifyCreated(shell.get());
51 RunEngine(shell.get(), std::move(sksl_config));
52
53 // Initially, we should have no SkSL cache
54 auto cache = PersistentCache::GetCacheForProcess()->LoadSkSLs();
55 ASSERT_EQ(cache.size(), 0u);
56
57 // Draw something to trigger shader compilations.
58 LayerTreeBuilder builder = [](std::shared_ptr<ContainerLayer> root) {
59 SkPath path;
60 path.addCircle(50, 50, 20);
61 auto physical_shape_layer = std::make_shared<PhysicalShapeLayer>(
62 SK_ColorRED, SK_ColorBLUE, 1.0f, path, Clip::antiAlias);
63 root->Add(physical_shape_layer);
64 };
65 PumpOneFrame(shell.get(), 100, 100, builder);
66 firstFrameLatch.Wait();
67 WaitForIO(shell.get());
68
69 // Some skp should be dumped due to shader compilations.
70 int skp_count = 0;
71 fml::FileVisitor skp_visitor = [&skp_count](const fml::UniqueFD& directory,
72 const std::string& filename) {
73 if (filename.size() >= 4 &&
74 filename.substr(filename.size() - 4, 4) == ".skp") {
75 skp_count += 1;
76 }
77 return true;
78 };
79 fml::VisitFilesRecursively(dir.fd(), skp_visitor);
80 ASSERT_GT(skp_count, 0);
81
82 // SkSL cache should be generated by the last run.
83 cache = PersistentCache::GetCacheForProcess()->LoadSkSLs();
84 ASSERT_GT(cache.size(), 0u);
85
86 // Run the engine again with cache_sksl = false and check that the previously
87 // generated SkSL cache is used for precompile.
88 PersistentCache::ResetCacheForProcess();
89 settings.cache_sksl = false;
90 settings.dump_skp_on_shader_compilation = true;
91 auto normal_config = RunConfiguration::InferFromSettings(settings);
92 normal_config.SetEntrypoint("emptyMain");
93 DestroyShell(std::move(shell));
94 shell = CreateShell(settings);
95 PlatformViewNotifyCreated(shell.get());
96 RunEngine(shell.get(), std::move(normal_config));
97 firstFrameLatch.Reset();
98 PumpOneFrame(shell.get(), 100, 100, builder);
99 firstFrameLatch.Wait();
100 WaitForIO(shell.get());
101
102// Shader precompilation from SKSL is not implemented on the Skia Vulkan
103// backend so don't run the second half of this test on Vulkan. This can get
104// removed if SKSL precompilation is implemented in the Skia Vulkan backend.
105#if !defined(SHELL_ENABLE_VULKAN)
106 // To check that all shaders are precompiled, verify that no new skp is dumped
107 // due to shader compilations.
108 int old_skp_count = skp_count;
109 skp_count = 0;
110 fml::VisitFilesRecursively(dir.fd(), skp_visitor);
111 ASSERT_EQ(skp_count, old_skp_count);
112#endif // !defined(SHELL_ENABLE_VULKAN)
113
114 // Remove all files generated
115 fml::FileVisitor remove_visitor = [&remove_visitor](
116 const fml::UniqueFD& directory,
117 const std::string& filename) {
118 if (fml::IsDirectory(directory, filename.c_str())) {
119 { // To trigger fml::~UniqueFD before fml::UnlinkDirectory
120 fml::UniqueFD sub_dir =
121 fml::OpenDirectoryReadOnly(directory, filename.c_str());
122 fml::VisitFiles(sub_dir, remove_visitor);
123 }
124 fml::UnlinkDirectory(directory, filename.c_str());
125 } else {
126 fml::UnlinkFile(directory, filename.c_str());
127 }
128 return true;
129 };
130 fml::VisitFiles(dir.fd(), remove_visitor);
131 DestroyShell(std::move(shell));
132}
133
134static void CheckTextSkData(sk_sp<SkData> data, const std::string& expected) {
135 std::string data_string(reinterpret_cast<const char*>(data->bytes()),
136 data->size());
137 ASSERT_EQ(data_string, expected);
138}
139
140static void ResetAssetManager() {
141 PersistentCache::SetAssetManager(nullptr);
142 ASSERT_EQ(PersistentCache::GetCacheForProcess()->LoadSkSLs().size(), 0u);
143}
144
145static void CheckTwoSkSLsAreLoaded() {
146 auto shaders = PersistentCache::GetCacheForProcess()->LoadSkSLs();
147 ASSERT_EQ(shaders.size(), 2u);
148}
149
150TEST_F(ShellTest, CanLoadSkSLsFromAsset) {
151 // Avoid polluting unit tests output by hiding INFO level logging.
152 fml::LogSettings warning_only = {fml::LOG_WARNING};
153 fml::ScopedSetLogSettings scoped_set_log_settings(warning_only);
154
155 // The SkSL key is Base32 encoded. "IE" is the encoding of "A" and "II" is the
156 // encoding of "B".
157 //
158 // The SkSL data is Base64 encoded. "eA==" is the encoding of "x" and "eQ=="
159 // is the encoding of "y".
160 const std::string kTestJson =
161 "{\n"
162 " \"data\": {\n"
163 " \"IE\": \"eA==\",\n"
164 " \"II\": \"eQ==\"\n"
165 " }\n"
166 "}\n";
167
168 // Temp dir for the asset.
169 fml::ScopedTemporaryDirectory asset_dir;
170
171 auto data = std::make_unique<fml::DataMapping>(
172 std::vector<uint8_t>{kTestJson.begin(), kTestJson.end()});
173 fml::WriteAtomically(asset_dir.fd(), PersistentCache::kAssetFileName, *data);
174
175 // 1st, test that RunConfiguration::InferFromSettings sets the asset manager.
176 ResetAssetManager();
177 auto settings = CreateSettingsForFixture();
178 settings.assets_path = asset_dir.path();
179 RunConfiguration::InferFromSettings(settings);
180 CheckTwoSkSLsAreLoaded();
181
182 // 2nd, test that the RunConfiguration constructor sets the asset manager.
183 // (Android is directly calling that constructor without InferFromSettings.)
184 ResetAssetManager();
185 auto asset_manager = std::make_shared<AssetManager>();
186 RunConfiguration config(nullptr, asset_manager);
187 asset_manager->PushBack(
188 std::make_unique<DirectoryAssetBundle>(fml::OpenDirectory(
189 asset_dir.path().c_str(), false, fml::FilePermission::kRead)));
190 CheckTwoSkSLsAreLoaded();
191
192 // 3rd, test the content of the SkSLs in the asset.
193 {
194 auto shaders = PersistentCache::GetCacheForProcess()->LoadSkSLs();
195 ASSERT_EQ(shaders.size(), 2u);
196
197 // Make sure that the 2 shaders are sorted by their keys. Their keys should
198 // be "A" and "B" (decoded from "II" and "IE").
199 if (shaders[0].first->bytes()[0] == 'B') {
200 std::swap(shaders[0], shaders[1]);
201 }
202
203 CheckTextSkData(shaders[0].first, "A");
204 CheckTextSkData(shaders[1].first, "B");
205 CheckTextSkData(shaders[0].second, "x");
206 CheckTextSkData(shaders[1].second, "y");
207 }
208
209 // Cleanup.
210 fml::UnlinkFile(asset_dir.fd(), PersistentCache::kAssetFileName);
211}
212
213TEST_F(ShellTest, CanRemoveOldPersistentCache) {
214 fml::ScopedTemporaryDirectory base_dir;
215 ASSERT_TRUE(base_dir.fd().is_valid());
216
217 fml::CreateDirectory(base_dir.fd(),
218 {"flutter_engine", GetFlutterEngineVersion(), "skia"},
219 fml::FilePermission::kReadWrite);
220
221 constexpr char kOldEngineVersion[] = "old";
222 auto old_created = fml::CreateDirectory(
223 base_dir.fd(), {"flutter_engine", kOldEngineVersion, "skia"},
224 fml::FilePermission::kReadWrite);
225 ASSERT_TRUE(old_created.is_valid());
226
227 PersistentCache::SetCacheDirectoryPath(base_dir.path());
228 PersistentCache::ResetCacheForProcess();
229
230 auto engine_dir = fml::OpenDirectoryReadOnly(base_dir.fd(), "flutter_engine");
231 auto current_dir =
232 fml::OpenDirectoryReadOnly(engine_dir, GetFlutterEngineVersion());
233 auto old_dir = fml::OpenDirectoryReadOnly(engine_dir, kOldEngineVersion);
234
235 ASSERT_TRUE(engine_dir.is_valid());
236 ASSERT_TRUE(current_dir.is_valid());
237 ASSERT_FALSE(old_dir.is_valid());
238
239 // Cleanup
240 fml::RemoveFilesInDirectory(base_dir.fd());
241}
242
243TEST_F(ShellTest, CanPurgePersistentCache) {
244 fml::ScopedTemporaryDirectory base_dir;
245 ASSERT_TRUE(base_dir.fd().is_valid());
246 auto cache_dir = fml::CreateDirectory(
247 base_dir.fd(),
248 {"flutter_engine", GetFlutterEngineVersion(), "skia", GetSkiaVersion()},
249 fml::FilePermission::kReadWrite);
250 PersistentCache::SetCacheDirectoryPath(base_dir.path());
251 PersistentCache::ResetCacheForProcess();
252
253 // Generate a dummy persistent cache.
254 fml::DataMapping test_data(std::string("test"));
255 ASSERT_TRUE(fml::WriteAtomically(cache_dir, "test", test_data));
256 auto file = fml::OpenFileReadOnly(cache_dir, "test");
257 ASSERT_TRUE(file.is_valid());
258
259 // Run engine with purge_persistent_cache to remove the dummy cache.
260 auto settings = CreateSettingsForFixture();
261 settings.purge_persistent_cache = true;
262 auto config = RunConfiguration::InferFromSettings(settings);
263 std::unique_ptr<Shell> shell = CreateShell(settings);
264 RunEngine(shell.get(), std::move(config));
265
266 // Verify that the dummy is purged.
267 file = fml::OpenFileReadOnly(cache_dir, "test");
268 ASSERT_FALSE(file.is_valid());
269
270 // Cleanup
271 fml::RemoveFilesInDirectory(base_dir.fd());
272 DestroyShell(std::move(shell));
273}
274
275} // namespace testing
276} // namespace flutter
277