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 | |
23 | namespace flutter { |
24 | namespace testing { |
25 | |
26 | static 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 | |
33 | TEST_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 | |
134 | static 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 | |
140 | static void ResetAssetManager() { |
141 | PersistentCache::SetAssetManager(nullptr); |
142 | ASSERT_EQ(PersistentCache::GetCacheForProcess()->LoadSkSLs().size(), 0u); |
143 | } |
144 | |
145 | static void CheckTwoSkSLsAreLoaded() { |
146 | auto shaders = PersistentCache::GetCacheForProcess()->LoadSkSLs(); |
147 | ASSERT_EQ(shaders.size(), 2u); |
148 | } |
149 | |
150 | TEST_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 | |
213 | TEST_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 | |
243 | TEST_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 | |