Flutter Engine
persistent_cache_unittests.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 "flutter/common/graphics/persistent_cache.h"
6 
7 #include <memory>
8 
9 #include "flutter/assets/directory_asset_bundle.h"
10 #include "flutter/flow/layers/container_layer.h"
11 #include "flutter/flow/layers/layer.h"
12 #include "flutter/flow/layers/physical_shape_layer.h"
13 #include "flutter/flow/layers/picture_layer.h"
14 #include "flutter/fml/command_line.h"
15 #include "flutter/fml/file.h"
16 #include "flutter/fml/log_settings.h"
17 #include "flutter/fml/unique_fd.h"
18 #include "flutter/shell/common/shell_test.h"
19 #include "flutter/shell/common/switches.h"
20 #include "flutter/shell/version/version.h"
21 #include "flutter/testing/testing.h"
22 #include "include/core/SkPicture.h"
23 
24 namespace flutter {
25 namespace testing {
26 
28 
29 static void WaitForIO(Shell* shell) {
30  std::promise<bool> io_task_finished;
32  [&io_task_finished]() { io_task_finished.set_value(true); });
33  io_task_finished.get_future().wait();
34 }
35 
36 static void WaitForRaster(Shell* shell) {
37  std::promise<bool> raster_task_finished;
39  [&raster_task_finished]() { raster_task_finished.set_value(true); });
40  raster_task_finished.get_future().wait();
41 }
42 
44 #if defined(WINUWP)
45  // TODO(cbracken): https://github.com/flutter/flutter/issues/90481
46  DISABLED_CacheSkSLWorks
47 #else
48  CacheSkSLWorks
49 #endif // defined(WINUWP)
50 ) {
51 
52  // Create a temp dir to store the persistent cache
56 
57  auto settings = CreateSettingsForFixture();
58  settings.cache_sksl = true;
59  settings.dump_skp_on_shader_compilation = true;
60 
61  fml::AutoResetWaitableEvent first_frame_latch;
62  settings.frame_rasterized_callback =
63  [&first_frame_latch](const FrameTiming& t) {
64  first_frame_latch.Signal();
65  };
66 
67  auto sksl_config = RunConfiguration::InferFromSettings(settings);
68  sksl_config.SetEntrypoint("emptyMain");
69  std::unique_ptr<Shell> shell = CreateShell(settings);
70  PlatformViewNotifyCreated(shell.get());
71  RunEngine(shell.get(), std::move(sksl_config));
72 
73  // Initially, we should have no SkSL cache
75  ASSERT_EQ(cache.size(), 0u);
76 
77  // Draw something to trigger shader compilations.
78  LayerTreeBuilder builder = [](std::shared_ptr<ContainerLayer> root) {
79  SkPath path;
80  path.addCircle(50, 50, 20);
81  auto physical_shape_layer = std::make_shared<PhysicalShapeLayer>(
82  SK_ColorRED, SK_ColorBLUE, 1.0f, path, Clip::antiAlias);
83  root->Add(physical_shape_layer);
84  };
85  PumpOneFrame(shell.get(), 100, 100, builder);
86  first_frame_latch.Wait();
87  WaitForIO(shell.get());
88 
89  // Some skp should be dumped due to shader compilations.
90  int skp_count = 0;
91  fml::FileVisitor skp_visitor = [&skp_count](const fml::UniqueFD& directory,
92  const std::string& filename) {
93  if (filename.size() >= 4 &&
94  filename.substr(filename.size() - 4, 4) == ".skp") {
95  skp_count += 1;
96  }
97  return true;
98  };
99  fml::VisitFilesRecursively(dir.fd(), skp_visitor);
100  ASSERT_GT(skp_count, 0);
101 
102  // SkSL cache should be generated by the last run.
104  ASSERT_GT(cache.size(), 0u);
105 
106  // Run the engine again with cache_sksl = false and check that the previously
107  // generated SkSL cache is used for precompile.
109  settings.cache_sksl = false;
110  settings.dump_skp_on_shader_compilation = true;
111  auto normal_config = RunConfiguration::InferFromSettings(settings);
112  normal_config.SetEntrypoint("emptyMain");
113  DestroyShell(std::move(shell));
114  shell = CreateShell(settings);
115  PlatformViewNotifyCreated(shell.get());
116  RunEngine(shell.get(), std::move(normal_config));
117  first_frame_latch.Reset();
118  PumpOneFrame(shell.get(), 100, 100, builder);
119  first_frame_latch.Wait();
120  WaitForIO(shell.get());
121 
122 // Shader precompilation from SkSL is not implemented on the Skia Vulkan
123 // backend so don't run the second half of this test on Vulkan. This can get
124 // removed if SkSL precompilation is implemented in the Skia Vulkan backend.
125 #if !defined(SHELL_ENABLE_VULKAN)
126  // To check that all shaders are precompiled, verify that no new skp is dumped
127  // due to shader compilations.
128  int old_skp_count = skp_count;
129  skp_count = 0;
130  fml::VisitFilesRecursively(dir.fd(), skp_visitor);
131  ASSERT_EQ(skp_count, old_skp_count);
132 #endif // !defined(SHELL_ENABLE_VULKAN)
133 
134  // Remove all files generated
136  DestroyShell(std::move(shell));
137 }
138 
139 TEST_F(PersistentCacheTest, CanPrecompileMetalShaders) {
140 #if !SHELL_ENABLE_METAL
141  GTEST_SKIP();
142 #endif // !SHELL_ENABLE_METAL
146 
147  auto settings = CreateSettingsForFixture();
148  settings.cache_sksl = true;
149  settings.dump_skp_on_shader_compilation = true;
150 
151  fml::AutoResetWaitableEvent first_frame_latch;
152  settings.frame_rasterized_callback =
153  [&first_frame_latch](const FrameTiming& t) {
154  first_frame_latch.Signal();
155  };
156 
157  auto sksl_config = RunConfiguration::InferFromSettings(settings);
158  sksl_config.SetEntrypoint("emptyMain");
159  std::unique_ptr<Shell> shell =
160  CreateShell(settings, //
161  GetTaskRunnersForFixture(), //
162  false, //
163  nullptr, //
164  false, //
166  );
167  PlatformViewNotifyCreated(shell.get());
168  RunEngine(shell.get(), std::move(sksl_config));
169 
170  // Initially, we should have no SkSL cache
171  {
172  auto empty_cache = PersistentCache::GetCacheForProcess()->LoadSkSLs();
173  ASSERT_EQ(empty_cache.size(), 0u);
174  }
175 
176  // Draw something to trigger shader compilations.
177  LayerTreeBuilder builder = [](std::shared_ptr<ContainerLayer> root) {
178  SkPath path;
179  path.addCircle(50, 50, 20);
180  auto physical_shape_layer = std::make_shared<PhysicalShapeLayer>(
181  SK_ColorRED, SK_ColorBLUE, 1.0f, path, Clip::antiAlias);
182  root->Add(physical_shape_layer);
183  };
184  PumpOneFrame(shell.get(), 100, 100, builder);
185  first_frame_latch.Wait();
186  WaitForRaster(shell.get());
187  WaitForIO(shell.get());
188 
189  // Assert that SkSLs have been generated.
190  auto filled_cache = PersistentCache::GetCacheForProcess()->LoadSkSLs();
191  ASSERT_GT(filled_cache.size(), 0u);
192 
193  // Remove all files generated.
195  DestroyShell(std::move(shell));
196 }
197 
198 static void CheckTextSkData(sk_sp<SkData> data, const std::string& expected) {
199  std::string data_string(reinterpret_cast<const char*>(data->bytes()),
200  data->size());
201  ASSERT_EQ(data_string, expected);
202 }
203 
204 static void ResetAssetManager() {
206  ASSERT_EQ(PersistentCache::GetCacheForProcess()->LoadSkSLs().size(), 0u);
207 }
208 
209 static void CheckTwoSkSLsAreLoaded() {
211  ASSERT_EQ(shaders.size(), 2u);
212 }
213 
215 #if defined(WINUWP)
216  // TODO(cbracken): https://github.com/flutter/flutter/issues/90481
217  DISABLED_CanLoadSkSLsFromAsset
218 #else
219  CanLoadSkSLsFromAsset
220 #endif // defined(WINUWP)
221 ) {
222 
223  // Avoid polluting unit tests output by hiding INFO level logging.
224  fml::LogSettings warning_only = {fml::LOG_WARNING};
225  fml::ScopedSetLogSettings scoped_set_log_settings(warning_only);
226 
227  // The SkSL key is Base32 encoded. "IE" is the encoding of "A" and "II" is the
228  // encoding of "B".
229  //
230  // The SkSL data is Base64 encoded. "eA==" is the encoding of "x" and "eQ=="
231  // is the encoding of "y".
232  const std::string kTestJson =
233  "{\n"
234  " \"data\": {\n"
235  " \"IE\": \"eA==\",\n"
236  " \"II\": \"eQ==\"\n"
237  " }\n"
238  "}\n";
239 
240  // Temp dir for the asset.
242 
243  auto data = std::make_unique<fml::DataMapping>(
244  std::vector<uint8_t>{kTestJson.begin(), kTestJson.end()});
246 
247  // 1st, test that RunConfiguration::InferFromSettings sets the asset manager.
249  auto settings = CreateSettingsForFixture();
250  settings.assets_path = asset_dir.path();
253 
254  // 2nd, test that the RunConfiguration constructor sets the asset manager.
255  // (Android is directly calling that constructor without InferFromSettings.)
257  auto asset_manager = std::make_shared<AssetManager>();
258  RunConfiguration config(nullptr, asset_manager);
259  asset_manager->PushBack(std::make_unique<DirectoryAssetBundle>(
260  fml::OpenDirectory(asset_dir.path().c_str(), false,
262  false));
264 
265  // 3rd, test the content of the SkSLs in the asset.
266  {
268  ASSERT_EQ(shaders.size(), 2u);
269 
270  // Make sure that the 2 shaders are sorted by their keys. Their keys should
271  // be "A" and "B" (decoded from "II" and "IE").
272  if (shaders[0].key->bytes()[0] == 'B') {
273  std::swap(shaders[0], shaders[1]);
274  }
275 
276  CheckTextSkData(shaders[0].key, "A");
277  CheckTextSkData(shaders[1].key, "B");
278  CheckTextSkData(shaders[0].value, "x");
279  CheckTextSkData(shaders[1].value, "y");
280  }
281 
282  // Cleanup.
284 }
285 
287 #if defined(WINUWP)
288  // TODO(cbracken): https://github.com/flutter/flutter/issues/90481
289  DISABLED_CanRemoveOldPersistentCache
290 #else
291  CanRemoveOldPersistentCache
292 #endif // defined(WINUWP)
293 ) {
294 
296  ASSERT_TRUE(base_dir.fd().is_valid());
297 
298  fml::CreateDirectory(base_dir.fd(),
299  {"flutter_engine", GetFlutterEngineVersion(), "skia"},
301 
302  constexpr char kOldEngineVersion[] = "old";
303  auto old_created = fml::CreateDirectory(
304  base_dir.fd(), {"flutter_engine", kOldEngineVersion, "skia"},
306  ASSERT_TRUE(old_created.is_valid());
307 
310 
311  auto engine_dir = fml::OpenDirectoryReadOnly(base_dir.fd(), "flutter_engine");
312  auto current_dir =
314  auto old_dir = fml::OpenDirectoryReadOnly(engine_dir, kOldEngineVersion);
315 
316  ASSERT_TRUE(engine_dir.is_valid());
317  ASSERT_TRUE(current_dir.is_valid());
318  ASSERT_FALSE(old_dir.is_valid());
319 
320  // Cleanup
321  fml::RemoveFilesInDirectory(base_dir.fd());
322 }
323 
325 #if defined(WINUWP)
326  // TODO(cbracken): https://github.com/flutter/flutter/issues/90481
327  DISABLED_CanPurgePersistentCache
328 #else
329  CanPurgePersistentCache
330 #endif // defined(WINUWP)
331 ) {
332 
334  ASSERT_TRUE(base_dir.fd().is_valid());
335  auto cache_dir = fml::CreateDirectory(
336  base_dir.fd(),
337  {"flutter_engine", GetFlutterEngineVersion(), "skia", GetSkiaVersion()},
341 
342  // Generate a dummy persistent cache.
343  fml::DataMapping test_data(std::string("test"));
344  ASSERT_TRUE(fml::WriteAtomically(cache_dir, "test", test_data));
345  auto file = fml::OpenFileReadOnly(cache_dir, "test");
346  ASSERT_TRUE(file.is_valid());
347 
348  // Run engine with purge_persistent_cache to remove the dummy cache.
349  auto settings = CreateSettingsForFixture();
350  settings.purge_persistent_cache = true;
351  auto config = RunConfiguration::InferFromSettings(settings);
352  std::unique_ptr<Shell> shell = CreateShell(settings);
353  RunEngine(shell.get(), std::move(config));
354 
355  // Verify that the dummy is purged.
356  file = fml::OpenFileReadOnly(cache_dir, "test");
357  ASSERT_FALSE(file.is_valid());
358 
359  // Cleanup
360  fml::RemoveFilesInDirectory(base_dir.fd());
361  DestroyShell(std::move(shell));
362 }
363 
365 #if defined(WINUWP)
366  // TODO(cbracken): https://github.com/flutter/flutter/issues/90481
367  DISABLED_PurgeAllowsFutureSkSLCache
368 #else
369  PurgeAllowsFutureSkSLCache
370 #endif // defined(WINUWP)
371 ) {
372 
373  sk_sp<SkData> shader_key = SkData::MakeWithCString("key");
374  sk_sp<SkData> shader_value = SkData::MakeWithCString("value");
375  std::string shader_filename = PersistentCache::SkKeyToFilePath(*shader_key);
376 
378  ASSERT_TRUE(base_dir.fd().is_valid());
381 
382  // Run engine with purge_persistent_cache and cache_sksl.
383  auto settings = CreateSettingsForFixture();
384  settings.purge_persistent_cache = true;
385  settings.cache_sksl = true;
386  auto config = RunConfiguration::InferFromSettings(settings);
387  std::unique_ptr<Shell> shell = CreateShell(settings);
388  RunEngine(shell.get(), std::move(config));
389  auto persistent_cache = PersistentCache::GetCacheForProcess();
390  ASSERT_EQ(persistent_cache->LoadSkSLs().size(), 0u);
391 
392  // Store the cache and verify it's valid.
393  StorePersistentCache(persistent_cache, *shader_key, *shader_value);
394  std::promise<bool> io_flushed;
395  shell->GetTaskRunners().GetIOTaskRunner()->PostTask(
396  [&io_flushed]() { io_flushed.set_value(true); });
397  io_flushed.get_future().get(); // Wait for the IO thread to flush the file.
398  ASSERT_GT(persistent_cache->LoadSkSLs().size(), 0u);
399 
400  // Cleanup
401  fml::RemoveFilesInDirectory(base_dir.fd());
402  DestroyShell(std::move(shell));
403 }
404 
405 } // namespace testing
406 } // namespace flutter
const std::string & path() const
Definition: file.h:146
const char * GetFlutterEngineVersion()
Definition: version.cc:11
DEF_SWITCHES_START snapshot asset path
Definition: switches.h:32
bool RemoveFilesInDirectory(const fml::UniqueFD &directory)
Definition: file.cc:102
std::function< bool(const fml::UniqueFD &directory, const std::string &filename)> FileVisitor
Definition: file.h:98
const TaskRunners & GetTaskRunners() const override
If callers wish to interact directly with any shell subcomponents, they must (on the platform thread)...
Definition: shell.cc:674
static void WaitForRaster(Shell *shell)
constexpr std::size_t size(T(&array)[N])
Definition: size.h:13
fml::RefPtr< fml::TaskRunner > GetRasterTaskRunner() const
Definition: task_runners.cc:42
void swap(scoped_nsprotocol< C > &p1, scoped_nsprotocol< C > &p2)
static constexpr char kAssetFileName[]
bool is_valid() const
Definition: unique_object.h:89
fml::UniqueFD OpenDirectory(const char *path, bool create_if_necessary, FilePermission permission)
Definition: file_posix.cc:96
static RunConfiguration InferFromSettings(const Settings &settings, fml::RefPtr< fml::TaskRunner > io_worker=nullptr)
Attempts to infer a run configuration from the settings object. This tries to create a run configurat...
static void SetAssetManager(std::shared_ptr< AssetManager > value)
uint8_t value
static void SetCacheDirectoryPath(std::string path)
Specifies all the configuration required by the runtime library to launch the root isolate...
constexpr LogSeverity LOG_WARNING
Definition: log_level.h:14
const UniqueFD & fd()
Definition: file.h:147
static fml::UniqueFD CreateDirectory(const fml::UniqueFD &base_directory, const std::vector< std::string > &components, FilePermission permission, size_t index)
Definition: file.cc:12
static void CheckTextSkData(sk_sp< SkData > data, const std::string &expected)
TEST_F(BackdropFilterLayerTest, PaintingEmptyLayerDies)
fml::RefPtr< fml::TaskRunner > GetIOTaskRunner() const
Definition: task_runners.cc:38
const char * GetSkiaVersion()
Definition: version.cc:15
fml::UniqueFD OpenDirectoryReadOnly(const fml::UniqueFD &base_directory, const char *path)
Definition: file.cc:97
static std::string SkKeyToFilePath(const SkData &key)
bool VisitFilesRecursively(const fml::UniqueFD &directory, const FileVisitor &visitor)
Definition: file.cc:71
bool UnlinkFile(const char *path)
Definition: file_posix.cc:170
static void ResetCacheForProcess()
static PersistentCache * GetCacheForProcess()
std::vector< SkSLCache > LoadSkSLs() const
Load all the SkSL shader caches in the right directory.
static void WaitForIO(Shell *shell)
bool WriteAtomically(const fml::UniqueFD &base_directory, const char *file_name, const Mapping &mapping)
Definition: file_posix.cc:190
virtual void PostTask(const fml::closure &task) override
Definition: task_runner.cc:24
fml::UniqueFD OpenFileReadOnly(const fml::UniqueFD &base_directory, const char *path)
Definition: file.cc:92