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/shell/common/persistent_cache.h"
6
7 #include <memory>
8 #include <string>
9 #include <string_view>
10
11 #include "flutter/fml/base32.h"
12 #include "flutter/fml/file.h"
13 #include "flutter/fml/make_copyable.h"
14 #include "flutter/fml/mapping.h"
15 #include "flutter/fml/paths.h"
16 #include "flutter/fml/trace_event.h"
17 #include "flutter/shell/version/version.h"
18
19 namespace flutter {
20
21 std::string PersistentCache::cache_base_path_;
22
SkKeyToFilePath(const SkData & data)23 static std::string SkKeyToFilePath(const SkData& data) {
24 if (data.data() == nullptr || data.size() == 0) {
25 return "";
26 }
27
28 std::string_view view(reinterpret_cast<const char*>(data.data()),
29 data.size());
30
31 auto encode_result = fml::Base32Encode(view);
32
33 if (!encode_result.first) {
34 return "";
35 }
36
37 return encode_result.second;
38 }
39
40 bool PersistentCache::gIsReadOnly = false;
41
GetCacheForProcess()42 PersistentCache* PersistentCache::GetCacheForProcess() {
43 static std::unique_ptr<PersistentCache> gPersistentCache;
44 static std::once_flag once = {};
45 std::call_once(
46 once, []() { gPersistentCache.reset(new PersistentCache(gIsReadOnly)); });
47 return gPersistentCache.get();
48 }
49
SetCacheDirectoryPath(std::string path)50 void PersistentCache::SetCacheDirectoryPath(std::string path) {
51 cache_base_path_ = path;
52 }
53
54 namespace {
MakeCacheDirectory(const std::string & global_cache_base_path,bool read_only)55 std::shared_ptr<fml::UniqueFD> MakeCacheDirectory(
56 const std::string& global_cache_base_path,
57 bool read_only) {
58 fml::UniqueFD cache_base_dir;
59 if (global_cache_base_path.length()) {
60 cache_base_dir = fml::OpenDirectory(global_cache_base_path.c_str(), false,
61 fml::FilePermission::kRead);
62 } else {
63 cache_base_dir = fml::paths::GetCachesDirectory();
64 }
65
66 if (cache_base_dir.is_valid()) {
67 return std::make_shared<fml::UniqueFD>(CreateDirectory(
68 cache_base_dir,
69 {"flutter_engine", GetFlutterEngineVersion(), "skia", GetSkiaVersion()},
70 read_only ? fml::FilePermission::kRead
71 : fml::FilePermission::kReadWrite));
72 } else {
73 return std::make_shared<fml::UniqueFD>();
74 }
75 }
76 } // namespace
77
PersistentCache(bool read_only)78 PersistentCache::PersistentCache(bool read_only)
79 : is_read_only_(read_only),
80 cache_directory_(MakeCacheDirectory(cache_base_path_, read_only)) {
81 if (!IsValid()) {
82 FML_LOG(WARNING) << "Could not acquire the persistent cache directory. "
83 "Caching of GPU resources on disk is disabled.";
84 }
85 }
86
87 PersistentCache::~PersistentCache() = default;
88
IsValid() const89 bool PersistentCache::IsValid() const {
90 return cache_directory_ && cache_directory_->is_valid();
91 }
92
93 // |GrContextOptions::PersistentCache|
load(const SkData & key)94 sk_sp<SkData> PersistentCache::load(const SkData& key) {
95 TRACE_EVENT0("flutter", "PersistentCacheLoad");
96 if (!IsValid()) {
97 return nullptr;
98 }
99 auto file_name = SkKeyToFilePath(key);
100 if (file_name.size() == 0) {
101 return nullptr;
102 }
103 auto file = fml::OpenFile(*cache_directory_, file_name.c_str(), false,
104 fml::FilePermission::kRead);
105 if (!file.is_valid()) {
106 return nullptr;
107 }
108 auto mapping = std::make_unique<fml::FileMapping>(file);
109 if (mapping->GetSize() == 0) {
110 return nullptr;
111 }
112
113 TRACE_EVENT0("flutter", "PersistentCacheLoadHit");
114 return SkData::MakeWithCopy(mapping->GetMapping(), mapping->GetSize());
115 }
116
PersistentCacheStore(fml::RefPtr<fml::TaskRunner> worker,std::shared_ptr<fml::UniqueFD> cache_directory,std::string key,std::unique_ptr<fml::Mapping> value)117 static void PersistentCacheStore(fml::RefPtr<fml::TaskRunner> worker,
118 std::shared_ptr<fml::UniqueFD> cache_directory,
119 std::string key,
120 std::unique_ptr<fml::Mapping> value) {
121 auto task =
122 fml::MakeCopyable([cache_directory, //
123 file_name = std::move(key), //
124 mapping = std::move(value) //
125 ]() mutable {
126 TRACE_EVENT0("flutter", "PersistentCacheStore");
127 if (!fml::WriteAtomically(*cache_directory, //
128 file_name.c_str(), //
129 *mapping) //
130 ) {
131 FML_DLOG(WARNING)
132 << "Could not write cache contents to persistent store.";
133 }
134 });
135
136 if (!worker) {
137 FML_LOG(WARNING)
138 << "The persistent cache has no available workers. Performing the task "
139 "on the current thread. This slow operation is going to occur on a "
140 "frame workload.";
141 task();
142 } else {
143 worker->PostTask(std::move(task));
144 }
145 }
146
147 // |GrContextOptions::PersistentCache|
store(const SkData & key,const SkData & data)148 void PersistentCache::store(const SkData& key, const SkData& data) {
149 stored_new_shaders_ = true;
150
151 if (is_read_only_) {
152 return;
153 }
154
155 if (!IsValid()) {
156 return;
157 }
158
159 auto file_name = SkKeyToFilePath(key);
160
161 if (file_name.size() == 0) {
162 return;
163 }
164
165 auto mapping = std::make_unique<fml::DataMapping>(
166 std::vector<uint8_t>{data.bytes(), data.bytes() + data.size()});
167
168 if (mapping == nullptr || mapping->GetSize() == 0) {
169 return;
170 }
171
172 PersistentCacheStore(GetWorkerTaskRunner(), cache_directory_,
173 std::move(file_name), std::move(mapping));
174 }
175
DumpSkp(const SkData & data)176 void PersistentCache::DumpSkp(const SkData& data) {
177 if (is_read_only_ || !IsValid()) {
178 FML_LOG(ERROR) << "Could not dump SKP from read-only or invalid persistent "
179 "cache.";
180 return;
181 }
182
183 std::stringstream name_stream;
184 auto ticks = fml::TimePoint::Now().ToEpochDelta().ToNanoseconds();
185 name_stream << "shader_dump_" << std::to_string(ticks) << ".skp";
186 std::string file_name = name_stream.str();
187 FML_LOG(INFO) << "Dumping " << file_name;
188 auto mapping = std::make_unique<fml::DataMapping>(
189 std::vector<uint8_t>{data.bytes(), data.bytes() + data.size()});
190 PersistentCacheStore(GetWorkerTaskRunner(), cache_directory_,
191 std::move(file_name), std::move(mapping));
192 }
193
AddWorkerTaskRunner(fml::RefPtr<fml::TaskRunner> task_runner)194 void PersistentCache::AddWorkerTaskRunner(
195 fml::RefPtr<fml::TaskRunner> task_runner) {
196 std::scoped_lock lock(worker_task_runners_mutex_);
197 worker_task_runners_.insert(task_runner);
198 }
199
RemoveWorkerTaskRunner(fml::RefPtr<fml::TaskRunner> task_runner)200 void PersistentCache::RemoveWorkerTaskRunner(
201 fml::RefPtr<fml::TaskRunner> task_runner) {
202 std::scoped_lock lock(worker_task_runners_mutex_);
203 auto found = worker_task_runners_.find(task_runner);
204 if (found != worker_task_runners_.end()) {
205 worker_task_runners_.erase(found);
206 }
207 }
208
GetWorkerTaskRunner() const209 fml::RefPtr<fml::TaskRunner> PersistentCache::GetWorkerTaskRunner() const {
210 fml::RefPtr<fml::TaskRunner> worker;
211
212 std::scoped_lock lock(worker_task_runners_mutex_);
213 if (!worker_task_runners_.empty()) {
214 worker = *worker_task_runners_.begin();
215 }
216
217 return worker;
218 }
219
220 } // namespace flutter
221