diff --git a/engine/src/flutter/fml/log_settings.cc b/engine/src/flutter/fml/log_settings.cc index d70525a72e0..057de5351e4 100644 --- a/engine/src/flutter/fml/log_settings.cc +++ b/engine/src/flutter/fml/log_settings.cc @@ -34,4 +34,13 @@ int GetMinLogLevel() { return std::min(state::g_log_settings.min_log_level, LOG_FATAL); } +ScopedSetLogSettings::ScopedSetLogSettings(const LogSettings& settings) { + old_settings_ = GetLogSettings(); + SetLogSettings(settings); +} + +ScopedSetLogSettings::~ScopedSetLogSettings() { + SetLogSettings(old_settings_); +} + } // namespace fml diff --git a/engine/src/flutter/fml/log_settings.h b/engine/src/flutter/fml/log_settings.h index 2cd14e028c3..cce9ce7bd90 100644 --- a/engine/src/flutter/fml/log_settings.h +++ b/engine/src/flutter/fml/log_settings.h @@ -35,6 +35,15 @@ LogSettings GetLogSettings(); // higher than LOG_FATAL. int GetMinLogLevel(); +class ScopedSetLogSettings { + public: + ScopedSetLogSettings(const LogSettings& settings); + ~ScopedSetLogSettings(); + + private: + LogSettings old_settings_; +}; + } // namespace fml #endif // FLUTTER_FML_LOG_SETTINGS_H_ diff --git a/engine/src/flutter/shell/common/persistent_cache.cc b/engine/src/flutter/shell/common/persistent_cache.cc index f663b081bad..0537905ed22 100644 --- a/engine/src/flutter/shell/common/persistent_cache.cc +++ b/engine/src/flutter/shell/common/persistent_cache.cc @@ -20,6 +20,7 @@ namespace flutter { std::string PersistentCache::cache_base_path_; +std::string PersistentCache::asset_path_; std::mutex PersistentCache::instance_mutex_; std::unique_ptr PersistentCache::gPersistentCache; @@ -90,7 +91,7 @@ static std::shared_ptr MakeCacheDirectory( std::vector components = { "flutter_engine", GetFlutterEngineVersion(), "skia", GetSkiaVersion()}; if (cache_sksl) { - components.push_back("sksl"); + components.push_back(PersistentCache::kSkSLSubdirName); } return std::make_shared( CreateDirectory(cache_base_dir, components, @@ -105,9 +106,6 @@ static std::shared_ptr MakeCacheDirectory( std::vector PersistentCache::LoadSkSLs() { TRACE_EVENT0("flutter", "PersistentCache::LoadSkSLs"); std::vector result; - if (!IsValid()) { - return result; - } fml::FileVisitor visitor = [&result](const fml::UniqueFD& directory, const std::string& filename) { std::pair decode_result = fml::Base32Decode(filename); @@ -126,7 +124,25 @@ std::vector PersistentCache::LoadSkSLs() { } return true; }; - fml::VisitFiles(*sksl_cache_directory_, visitor); + + // Only visit sksl_cache_directory_ if this persistent cache is valid. + // However, we'd like to continue visit the asset dir even if this persistent + // cache is invalid. + if (IsValid()) { + fml::VisitFiles(*sksl_cache_directory_, visitor); + } + + fml::UniqueFD root_asset_dir = fml::OpenDirectory(asset_path_.c_str(), false, + fml::FilePermission::kRead); + fml::UniqueFD sksl_asset_dir = + fml::OpenDirectoryReadOnly(root_asset_dir, kSkSLSubdirName); + if (sksl_asset_dir.is_valid()) { + FML_LOG(INFO) << "Found sksl asset directory. Loading SkSLs from it..."; + fml::VisitFiles(sksl_asset_dir, visitor); + } else { + FML_LOG(INFO) << "No sksl asset directory found."; + } + return result; } @@ -283,4 +299,9 @@ fml::RefPtr PersistentCache::GetWorkerTaskRunner() const { return worker; } +void PersistentCache::UpdateAssetPath(const std::string& path) { + FML_LOG(INFO) << "PersistentCache::UpdateAssetPath: " << path; + asset_path_ = path; +} + } // namespace flutter diff --git a/engine/src/flutter/shell/common/persistent_cache.h b/engine/src/flutter/shell/common/persistent_cache.h index c48627fdbfc..5cfb0afb75b 100644 --- a/engine/src/flutter/shell/common/persistent_cache.h +++ b/engine/src/flutter/shell/common/persistent_cache.h @@ -65,12 +65,18 @@ class PersistentCache : public GrContextOptions::PersistentCache { /// Load all the SkSL shader caches in the right directory. std::vector LoadSkSLs(); + /// Update the asset path from which PersistentCache can load SkLSs. + static void UpdateAssetPath(const std::string& path); + static bool cache_sksl() { return cache_sksl_; } static void SetCacheSkSL(bool value); static void MarkStrategySet() { strategy_set_ = true; } + static constexpr char kSkSLSubdirName[] = "sksl"; + private: static std::string cache_base_path_; + static std::string asset_path_; static std::mutex instance_mutex_; static std::unique_ptr gPersistentCache; diff --git a/engine/src/flutter/shell/common/persistent_cache_unittests.cc b/engine/src/flutter/shell/common/persistent_cache_unittests.cc index 4948a3359bc..597727af3b8 100644 --- a/engine/src/flutter/shell/common/persistent_cache_unittests.cc +++ b/engine/src/flutter/shell/common/persistent_cache_unittests.cc @@ -10,6 +10,7 @@ #include "flutter/flow/layers/picture_layer.h" #include "flutter/fml/command_line.h" #include "flutter/fml/file.h" +#include "flutter/fml/log_settings.h" #include "flutter/fml/unique_fd.h" #include "flutter/shell/common/persistent_cache.h" #include "flutter/shell/common/shell_test.h" @@ -123,5 +124,99 @@ TEST_F(ShellTest, CacheSkSLWorks) { DestroyShell(std::move(shell)); } +static void CheckTextSkData(sk_sp data, const std::string& expected) { + std::string data_string(reinterpret_cast(data->bytes()), + data->size()); + ASSERT_EQ(data_string, expected); +} + +void ResetAssetPath() { + PersistentCache::UpdateAssetPath("some_path_that_does_not_exist"); + ASSERT_EQ(PersistentCache::GetCacheForProcess()->LoadSkSLs().size(), 0u); +} + +void CheckTwoSkSLsAreLoaded() { + auto shaders = PersistentCache::GetCacheForProcess()->LoadSkSLs(); + ASSERT_EQ(shaders.size(), 2u); +} + +TEST_F(ShellTest, CanLoadSkSLsFromAsset) { + // Avoid polluting unit tests output by hiding INFO level logging. + fml::LogSettings warning_only = {fml::LOG_WARNING}; + fml::ScopedSetLogSettings scoped_set_log_settings(warning_only); + + // Create an empty shell to test its service protocol handlers. + auto empty_settings = CreateSettingsForFixture(); + auto empty_config = RunConfiguration::InferFromSettings(empty_settings); + std::unique_ptr empty_shell = CreateShell(empty_settings); + + // Temp dir for the asset. + fml::ScopedTemporaryDirectory asset_dir; + fml::UniqueFD sksl_asset_dir = + fml::OpenDirectory(asset_dir.fd(), PersistentCache::kSkSLSubdirName, true, + fml::FilePermission::kReadWrite); + + // The SkSL filenames are Base32 encoded strings. "IE" is the encoding of "A" + // and "II" is the encoding of "B". + const std::string kFileNames[2] = {"IE", "II"}; + const std::string kFileData[2] = {"x", "y"}; + + // Prepare 2 SkSL files in the asset directory. + for (int i = 0; i < 2; i += 1) { + auto data = std::make_unique( + std::vector{kFileData[i].begin(), kFileData[i].end()}); + fml::WriteAtomically(sksl_asset_dir, kFileNames[i].c_str(), *data); + } + + // 1st, test that RunConfiguration::InferFromSettings sets the path. + ResetAssetPath(); + auto settings = CreateSettingsForFixture(); + settings.assets_path = asset_dir.path(); + RunConfiguration::InferFromSettings(settings); + CheckTwoSkSLsAreLoaded(); + + // 2nd, test that Shell::OnServiceProtocolSetAssetBundlePath sets the path. + ResetAssetPath(); + ServiceProtocol::Handler::ServiceProtocolMap params; + rapidjson::Document document; + params["assetDirectory"] = asset_dir.path(); + OnServiceProtocol( + empty_shell.get(), ShellTest::ServiceProtocolEnum::kSetAssetBundlePath, + empty_shell->GetTaskRunners().GetUITaskRunner(), params, document); + CheckTwoSkSLsAreLoaded(); + + // 3rd, test that Shell::OnServiceProtocolRunInView sets the path. + ResetAssetPath(); + params["assetDirectory"] = asset_dir.path(); + params["mainScript"] = "no_such_script.dart"; + OnServiceProtocol( + empty_shell.get(), ShellTest::ServiceProtocolEnum::kSetAssetBundlePath, + empty_shell->GetTaskRunners().GetUITaskRunner(), params, document); + CheckTwoSkSLsAreLoaded(); + + // 4th, test the content of the SkSLs in the asset. + { + auto shaders = PersistentCache::GetCacheForProcess()->LoadSkSLs(); + ASSERT_EQ(shaders.size(), 2u); + + // Make sure that the 2 shaders are sorted by their keys. Their keys should + // be "A" and "B" (decoded from "II" and "IE"). + if (shaders[0].first->bytes()[0] == 'B') { + std::swap(shaders[0], shaders[1]); + } + + CheckTextSkData(shaders[0].first, "A"); + CheckTextSkData(shaders[1].first, "B"); + CheckTextSkData(shaders[0].second, "x"); + CheckTextSkData(shaders[1].second, "y"); + } + + // Cleanup. + DestroyShell(std::move(empty_shell)); + fml::UnlinkFile(sksl_asset_dir, kFileNames[0].c_str()); + fml::UnlinkFile(sksl_asset_dir, kFileNames[1].c_str()); + fml::UnlinkDirectory(asset_dir.fd(), PersistentCache::kSkSLSubdirName); +} + } // namespace testing } // namespace flutter diff --git a/engine/src/flutter/shell/common/run_configuration.cc b/engine/src/flutter/shell/common/run_configuration.cc index de0aa1adc91..e87c1f0bb57 100644 --- a/engine/src/flutter/shell/common/run_configuration.cc +++ b/engine/src/flutter/shell/common/run_configuration.cc @@ -10,6 +10,7 @@ #include "flutter/fml/file.h" #include "flutter/fml/unique_fd.h" #include "flutter/runtime/dart_vm.h" +#include "flutter/shell/common/persistent_cache.h" namespace flutter { @@ -26,6 +27,7 @@ RunConfiguration RunConfiguration::InferFromSettings( asset_manager->PushBack( std::make_unique(fml::OpenDirectory( settings.assets_path.c_str(), false, fml::FilePermission::kRead))); + PersistentCache::UpdateAssetPath(settings.assets_path); return {IsolateConfiguration::InferFromSettings(settings, asset_manager, io_worker), diff --git a/engine/src/flutter/shell/common/shell.cc b/engine/src/flutter/shell/common/shell.cc index 71ab4a95094..16a76bb9a9e 100644 --- a/engine/src/flutter/shell/common/shell.cc +++ b/engine/src/flutter/shell/common/shell.cc @@ -1312,6 +1312,7 @@ bool Shell::OnServiceProtocolRunInView( configuration.AddAssetResolver( std::make_unique(fml::OpenDirectory( asset_directory_path.c_str(), false, fml::FilePermission::kRead))); + PersistentCache::UpdateAssetPath(asset_directory_path); auto& allocator = response.GetAllocator(); response.SetObject(); @@ -1406,6 +1407,7 @@ bool Shell::OnServiceProtocolSetAssetBundlePath( asset_manager->PushFront(std::make_unique( fml::OpenDirectory(params.at("assetDirectory").data(), false, fml::FilePermission::kRead))); + PersistentCache::UpdateAssetPath(params.at("assetDirectory").data()); if (engine_->UpdateAssetManager(std::move(asset_manager))) { response.AddMember("type", "Success", allocator); diff --git a/engine/src/flutter/shell/common/shell_test.cc b/engine/src/flutter/shell/common/shell_test.cc index 378375c5eac..07ae5b3f7f9 100644 --- a/engine/src/flutter/shell/common/shell_test.cc +++ b/engine/src/flutter/shell/common/shell_test.cc @@ -202,17 +202,28 @@ bool ShellTest::GetNeedsReportTimings(Shell* shell) { return shell->needs_report_timings_; } -void ShellTest::OnServiceProtocolGetSkSLs( +void ShellTest::OnServiceProtocol( Shell* shell, + ServiceProtocolEnum some_protocol, + fml::RefPtr task_runner, const ServiceProtocol::Handler::ServiceProtocolMap& params, rapidjson::Document& response) { std::promise finished; - fml::TaskRunner::RunNowOrPostTask(shell->GetTaskRunners().GetIOTaskRunner(), - [shell, params, &response, &finished]() { - shell->OnServiceProtocolGetSkSLs( - params, response); - finished.set_value(true); - }); + fml::TaskRunner::RunNowOrPostTask( + task_runner, [shell, some_protocol, params, &response, &finished]() { + switch (some_protocol) { + case ServiceProtocolEnum::kGetSkSLs: + shell->OnServiceProtocolGetSkSLs(params, response); + break; + case ServiceProtocolEnum::kSetAssetBundlePath: + shell->OnServiceProtocolSetAssetBundlePath(params, response); + break; + case ServiceProtocolEnum::kRunInView: + shell->OnServiceProtocolRunInView(params, response); + break; + } + finished.set_value(true); + }); finished.get_future().wait(); } diff --git a/engine/src/flutter/shell/common/shell_test.h b/engine/src/flutter/shell/common/shell_test.h index 680db7d349c..92c917c0a05 100644 --- a/engine/src/flutter/shell/common/shell_test.h +++ b/engine/src/flutter/shell/common/shell_test.h @@ -13,6 +13,7 @@ #include "flutter/fml/macros.h" #include "flutter/fml/time/time_point.h" #include "flutter/lib/ui/window/platform_message.h" +#include "flutter/shell/common/persistent_cache.h" #include "flutter/shell/common/run_configuration.h" #include "flutter/shell/common/shell.h" #include "flutter/shell/common/thread_host.h" @@ -75,11 +76,19 @@ class ShellTest : public ThreadTest { static bool GetNeedsReportTimings(Shell* shell); static void SetNeedsReportTimings(Shell* shell, bool value); + enum ServiceProtocolEnum { + kGetSkSLs, + kSetAssetBundlePath, + kRunInView, + }; + // Helper method to test private method Shell::OnServiceProtocolGetSkSLs. // (ShellTest is a friend class of Shell.) We'll also make sure that it is - // running on the UI thread. - static void OnServiceProtocolGetSkSLs( + // running on the correct task_runner. + static void OnServiceProtocol( Shell* shell, + ServiceProtocolEnum some_protocol, + fml::RefPtr task_runner, const ServiceProtocol::Handler::ServiceProtocolMap& params, rapidjson::Document& response); diff --git a/engine/src/flutter/shell/common/shell_unittests.cc b/engine/src/flutter/shell/common/shell_unittests.cc index c9c1345541f..b847a442779 100644 --- a/engine/src/flutter/shell/common/shell_unittests.cc +++ b/engine/src/flutter/shell/common/shell_unittests.cc @@ -1154,7 +1154,9 @@ TEST_F(ShellTest, OnServiceProtocolGetSkSLsWorks) { std::unique_ptr shell = CreateShell(settings); ServiceProtocol::Handler::ServiceProtocolMap empty_params; rapidjson::Document document; - OnServiceProtocolGetSkSLs(shell.get(), empty_params, document); + OnServiceProtocol(shell.get(), ServiceProtocolEnum::kGetSkSLs, + shell->GetTaskRunners().GetIOTaskRunner(), empty_params, + document); rapidjson::StringBuffer buffer; rapidjson::Writer writer(buffer); document.Accept(writer); diff --git a/engine/src/flutter/testing/fuchsia/run_tests.sh b/engine/src/flutter/testing/fuchsia/run_tests.sh index 5b40eb4e846..7fa6d38b68e 100755 --- a/engine/src/flutter/testing/fuchsia/run_tests.sh +++ b/engine/src/flutter/testing/fuchsia/run_tests.sh @@ -80,9 +80,10 @@ echo "$(date) START:runtime_tests -----------------------------------" -t runtime_tests # TODO(https://github.com/flutter/flutter/issues/53399): Re-enable -# OnServiceProtocolGetSkSLsWorks once it passes on Fuchsia. +# OnServiceProtocolGetSkSLsWorks and CanLoadSkSLsFromAsset once they pass on +# Fuchsia. echo "$(date) START:shell_tests -------------------------------------" ./fuchsia_ctl -d $device_name test \ -f shell_tests-0.far \ -t shell_tests \ - -a "--gtest_filter=-ShellTest.CacheSkSLWorks:ShellTest.SetResourceCacheSize*:ShellTest.OnServiceProtocolGetSkSLsWorks" + -a "--gtest_filter=-ShellTest.CacheSkSLWorks:ShellTest.SetResourceCacheSize*:ShellTest.OnServiceProtocolGetSkSLsWorks:ShellTest.CanLoadSkSLsFromAsset"