5#include "flutter/common/graphics/persistent_cache.h"
13#include "flutter/fml/base32.h"
14#include "flutter/fml/file.h"
15#include "flutter/fml/hex_codec.h"
16#include "flutter/fml/logging.h"
17#include "flutter/fml/make_copyable.h"
18#include "flutter/fml/mapping.h"
19#include "flutter/fml/paths.h"
20#include "flutter/fml/trace_event.h"
21#include "flutter/shell/common/base64.h"
22#include "flutter/shell/version/version.h"
23#include "openssl/sha.h"
24#include "rapidjson/document.h"
29std::string PersistentCache::cache_base_path_;
31std::shared_ptr<AssetManager> PersistentCache::asset_manager_;
33std::mutex PersistentCache::instance_mutex_;
34std::unique_ptr<PersistentCache> PersistentCache::gPersistentCache;
37 if (
key.data() ==
nullptr ||
key.size() == 0) {
41 uint8_t sha_digest[SHA_DIGEST_LENGTH];
42 SHA1(
static_cast<const uint8_t*
>(
key.data()),
key.size(), sha_digest);
44 std::string_view view(
reinterpret_cast<const char*
>(sha_digest),
51std::atomic<bool> PersistentCache::cache_sksl_ =
false;
52std::atomic<bool> PersistentCache::strategy_set_ =
false;
55 if (strategy_set_ &&
value != cache_sksl_) {
56 FML_LOG(
ERROR) <<
"Cache SkSL can only be set before the "
57 "GrContextOptions::fShaderCacheStrategy is set.";
64 std::scoped_lock lock(instance_mutex_);
65 if (gPersistentCache ==
nullptr) {
68 return gPersistentCache.get();
72 std::scoped_lock lock(instance_mutex_);
74 strategy_set_ =
false;
78 cache_base_path_ = std::move(
path);
87 std::promise<bool> removed;
88 GetWorkerTaskRunner()->
PostTask([&removed,
89 cache_directory = cache_directory_]() {
90 if (cache_directory->is_valid()) {
92 FML_LOG(INFO) <<
"Purge persistent cache.";
93 fml::FileVisitor delete_file = [](const fml::UniqueFD& directory,
94 const std::string& filename) {
96 if (fml::IsDirectory(directory, filename.c_str())) {
99 return fml::UnlinkFile(directory, filename.c_str());
101 removed.set_value(VisitFilesRecursively(*cache_directory, delete_file));
103 removed.set_value(
false);
106 return removed.get_future().get();
111constexpr char kEngineComponent[] =
"flutter_engine";
113static void FreeOldCacheDirectory(
const fml::UniqueFD& cache_base_dir) {
120 const std::string& filename) {
124 if (
dir.is_valid()) {
132static std::shared_ptr<fml::UniqueFD> MakeCacheDirectory(
133 const std::string& global_cache_base_path,
137 if (global_cache_base_path.length()) {
145 FreeOldCacheDirectory(cache_base_dir);
146 std::vector<std::string> components = {
149 components.push_back(PersistentCache::kSkSLSubdirName);
151 return std::make_shared<fml::UniqueFD>(
156 return std::make_shared<fml::UniqueFD>();
163 if (!decode_result.first) {
167 const std::string& data_string = decode_result.second;
175 error = Base64::Decode(input.c_str(), input.length(),
nullptr, &output_len);
176 if (
error != Base64::Error::kNone) {
183 void* output =
data->writable_data();
184 error = Base64::Decode(input.c_str(), input.length(), output, &output_len);
185 if (
error != Base64::Error::kNone) {
198 auto known_sksls = LoadSkSLs();
200 FML_TRACE_EVENT(
"flutter",
"PersistentCache::PrecompileKnownSkSLs",
"count",
203 if (context ==
nullptr) {
207 size_t precompiled_count = 0;
208 for (
const auto& sksl : known_sksls) {
216 reinterpret_cast<int64_t
>(
this),
217 "Successful", precompiled_count);
218 return precompiled_count;
221std::vector<PersistentCache::SkSLCache> PersistentCache::LoadSkSLs()
const {
223 std::vector<PersistentCache::SkSLCache>
result;
225 const std::string& filename) {
227 if (
cache.key !=
nullptr &&
cache.value !=
nullptr) {
248 std::unique_ptr<fml::Mapping> mapping =
nullptr;
249 if (asset_manager_ !=
nullptr) {
250 mapping = asset_manager_->GetAsMapping(kAssetFileName);
252 if (mapping ==
nullptr) {
253 FML_LOG(INFO) <<
"No sksl asset found.";
255 FML_LOG(INFO) <<
"Found sksl asset. Loading SkSLs from it...";
256 rapidjson::Document json_doc;
257 rapidjson::ParseResult parse_result =
258 json_doc.Parse(
reinterpret_cast<const char*
>(mapping->GetMapping()),
260 if (parse_result.IsError()) {
261 FML_LOG(
ERROR) <<
"Failed to parse json file: " << kAssetFileName;
263 for (
auto& item : json_doc[
"data"].GetObject()) {
266 if (
key !=
nullptr && sksl !=
nullptr) {
269 FML_LOG(
ERROR) <<
"Failed to load: " << item.name.GetString();
278PersistentCache::PersistentCache(
bool read_only)
279 : is_read_only_(read_only),
280 cache_directory_(MakeCacheDirectory(cache_base_path_, read_only, false)),
281 sksl_cache_directory_(
282 MakeCacheDirectory(cache_base_path_, read_only,
true)) {
284 FML_LOG(WARNING) <<
"Could not acquire the persistent cache directory. "
285 "Caching of GPU resources on disk is disabled.";
291bool PersistentCache::IsValid()
const {
292 return cache_directory_ && cache_directory_->is_valid();
295PersistentCache::SkSLCache PersistentCache::LoadFile(
297 const std::string& file_name,
301 if (!
file.is_valid()) {
304 auto mapping = std::make_unique<fml::FileMapping>(
file);
305 if (mapping->GetSize() <
sizeof(CacheObjectHeader)) {
308 const CacheObjectHeader*
header =
309 reinterpret_cast<const CacheObjectHeader*
>(mapping->GetMapping());
312 FML_LOG(INFO) <<
"Persistent cache header is corrupt: " << file_name;
315 if (mapping->GetSize() <
sizeof(CacheObjectHeader) +
header->key_size) {
316 FML_LOG(INFO) <<
"Persistent cache size is corrupt: " << file_name;
321 mapping->GetMapping() +
sizeof(CacheObjectHeader),
header->key_size);
323 size_t value_offset =
sizeof(CacheObjectHeader) +
header->key_size;
325 mapping->GetSize() - value_offset);
336 if (file_name.empty()) {
340 PersistentCache::LoadFile(*cache_directory_, file_name,
false).value;
349 const std::shared_ptr<fml::UniqueFD>& cache_directory,
351 std::unique_ptr<fml::Mapping> value) {
355 file_name = std::move(
key),
356 mapping = std::move(
value)
363 FML_LOG(WARNING) <<
"Could not write cache contents to persistent store.";
369 <<
"The persistent cache has no available workers. Performing the task "
370 "on the current thread. This slow operation is going to occur on a "
374 worker->PostTask(std::move(task));
382 uint8_t* mapping_buf =
reinterpret_cast<uint8_t*
>(malloc(
total_size));
386 auto mapping = std::make_unique<fml::MallocMapping>(mapping_buf,
total_size);
391 memcpy(mapping_buf,
key.data(),
key.size());
392 mapping_buf +=
key.size();
393 memcpy(mapping_buf,
data.data(),
data.size());
400 stored_new_shaders_ =
true;
412 if (file_name.empty()) {
422 cache_sksl_ ? sksl_cache_directory_ : cache_directory_,
423 std::move(file_name), std::move(mapping));
427 if (is_read_only_ || !IsValid()) {
428 FML_LOG(
ERROR) <<
"Could not dump SKP from read-only or invalid persistent "
433 std::stringstream name_stream;
435 name_stream <<
"shader_dump_" << std::to_string(ticks) <<
".skp";
436 std::string file_name = name_stream.str();
437 FML_LOG(INFO) <<
"Dumping " << file_name;
438 auto mapping = std::make_unique<fml::DataMapping>(
439 std::vector<uint8_t>{
data.bytes(),
data.bytes() +
data.size()});
441 std::move(file_name), std::move(mapping));
446 std::scoped_lock lock(worker_task_runners_mutex_);
447 worker_task_runners_.insert(task_runner);
452 std::scoped_lock lock(worker_task_runners_mutex_);
453 auto found = worker_task_runners_.find(task_runner);
454 if (found != worker_task_runners_.end()) {
455 worker_task_runners_.erase(found);
462 std::scoped_lock lock(worker_task_runners_mutex_);
463 if (!worker_task_runners_.empty()) {
464 worker = *worker_task_runners_.begin();
472 asset_manager_ = std::move(
value);
475std::vector<std::unique_ptr<fml::Mapping>>
477 if (!asset_manager_) {
479 <<
"PersistentCache::GetSkpsFromAssetManager: Asset manager not set!";
480 return std::vector<std::unique_ptr<fml::Mapping>>();
482 return asset_manager_->GetAsMappings(
".*\\.skp$",
"shaders");
static size_t total_size(SkSBlockAllocator< N > &pool)
Type::kYUV Type::kRGBA() int(0.7 *637)
PersistentCache()=default
bool precompileShader(const SkData &key, const SkData &data)
static sk_sp< SkData > MakeUninitialized(size_t length)
static sk_sp< SkData > MakeWithCopy(const void *data, size_t length)
static void SetAssetManager(std::shared_ptr< AssetManager > value)
void RemoveWorkerTaskRunner(const fml::RefPtr< fml::TaskRunner > &task_runner)
std::vector< std::unique_ptr< fml::Mapping > > GetSkpsFromAssetManager() const
static void SetCacheDirectoryPath(std::string path)
sk_sp< SkData > load(const SkData &key) override
static PersistentCache * GetCacheForProcess()
void DumpSkp(const SkData &data)
static void SetCacheSkSL(bool value)
static void ResetCacheForProcess()
void AddWorkerTaskRunner(const fml::RefPtr< fml::TaskRunner > &task_runner)
void store(const SkData &key, const SkData &data) override
~PersistentCache() override
static std::string SkKeyToFilePath(const SkData &key)
static std::unique_ptr< fml::MallocMapping > BuildCacheObject(const SkData &key, const SkData &data)
virtual void PostTask(const fml::closure &task) override
constexpr int64_t ToNanoseconds() const
TimeDelta ToEpochDelta() const
const uint8_t uint32_t uint32_t GError ** error
#define FML_LOG(severity)
#define FML_CHECK(condition)
const char * GetSkiaVersion()
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 to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace Enable an endless trace buffer The default is a ring buffer This is useful when very old events need to viewed For during application launch Memory usage will continue to grow indefinitely however Start app with an specific route defined on the framework flutter assets Path to the Flutter assets directory enable service port Allow the VM service to fallback to automatic port selection if binding to a specified port fails trace Trace early application lifecycle Automatically switches to an endless trace buffer trace skia Filters out all Skia trace event categories except those that are specified in this comma separated list dump skp on shader Automatically dump the skp that triggers new shader compilations This is useful for writing custom ShaderWarmUp to reduce jank By this is not enabled to reduce the overhead purge persistent cache
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
const char * GetFlutterEngineVersion()
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 to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace Enable an endless trace buffer The default is a ring buffer This is useful when very old events need to viewed For during application launch Memory usage will continue to grow indefinitely however Start app with an specific route defined on the framework flutter assets Path to the Flutter assets directory enable service port Allow the VM service to fallback to automatic port selection if binding to a specified port fails trace Trace early application lifecycle Automatically switches to an endless trace buffer trace skia Filters out all Skia trace event categories except those that are specified in this comma separated list dump skp on shader Automatically dump the skp that triggers new shader compilations This is useful for writing custom ShaderWarmUp to reduce jank By this is not enabled to reduce the overhead purge persistent Remove all existing persistent cache This is mainly for debugging purposes such as reproducing the shader compilation jank trace to file
sk_sp< SkData > ParseBase32(const std::string &input)
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot data
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 to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace Enable an endless trace buffer The default is a ring buffer This is useful when very old events need to viewed For during application launch Memory usage will continue to grow indefinitely however Start app with an specific route defined on the framework flutter assets dir
sk_sp< SkData > ParseBase64(const std::string &input)
static void PersistentCacheStore(const fml::RefPtr< fml::TaskRunner > &worker, const std::shared_ptr< fml::UniqueFD > &cache_directory, std::string key, std::unique_ptr< fml::Mapping > value)
fml::UniqueFD GetCachesDirectory()
std::string HexEncode(std::string_view input)
fml::UniqueFD OpenFileReadOnly(const fml::UniqueFD &base_directory, const char *path)
bool VisitFiles(const fml::UniqueFD &directory, const FileVisitor &visitor)
bool WriteAtomically(const fml::UniqueFD &base_directory, const char *file_name, const Mapping &mapping)
fml::UniqueFD OpenDirectoryReadOnly(const fml::UniqueFD &base_directory, const char *path)
std::pair< bool, std::string > Base32Decode(const std::string &input)
fml::UniqueFD OpenDirectory(const char *path, bool create_if_necessary, FilePermission permission)
internal::CopyableLambda< T > MakeCopyable(T lambda)
bool RemoveDirectoryRecursively(const fml::UniqueFD &parent, const char *directory_name)
std::function< bool(const fml::UniqueFD &directory, const std::string &filename)> FileVisitor
static const char header[]
#define TRACE_EVENT0(category_group, name)
#define FML_TRACE_COUNTER(category_group, name, counter_id, arg1,...)
#define TRACE_EVENT_INSTANT0(category_group, name)
#define FML_TRACE_EVENT(category_group, name,...)