diff --git a/runtime/service_protocol.cc b/runtime/service_protocol.cc index 16d574de8f8..5c7ea0279a4 100644 --- a/runtime/service_protocol.cc +++ b/runtime/service_protocol.cc @@ -33,6 +33,8 @@ const std::string_view ServiceProtocol::kSetAssetBundlePathExtensionName = "_flutter.setAssetBundlePath"; const std::string_view ServiceProtocol::kGetDisplayRefreshRateExtensionName = "_flutter.getDisplayRefreshRate"; +const std::string_view ServiceProtocol::kGetSkSLsExtensionName = + "_flutter.getSkSLs"; static constexpr std::string_view kViewIdPrefx = "_flutterView/"; static constexpr std::string_view kListViewsExtensionName = @@ -50,6 +52,7 @@ ServiceProtocol::ServiceProtocol() kFlushUIThreadTasksExtensionName, kSetAssetBundlePathExtensionName, kGetDisplayRefreshRateExtensionName, + kGetSkSLsExtensionName, }), handlers_mutex_(fml::SharedMutex::Create()) {} diff --git a/runtime/service_protocol.h b/runtime/service_protocol.h index ce2a6845c24..c66a836a2c5 100644 --- a/runtime/service_protocol.h +++ b/runtime/service_protocol.h @@ -27,6 +27,7 @@ class ServiceProtocol { static const std::string_view kFlushUIThreadTasksExtensionName; static const std::string_view kSetAssetBundlePathExtensionName; static const std::string_view kGetDisplayRefreshRateExtensionName; + static const std::string_view kGetSkSLsExtensionName; class Handler { public: diff --git a/shell/common/persistent_cache.cc b/shell/common/persistent_cache.cc index 66690971ace..f663b081bad 100644 --- a/shell/common/persistent_cache.cc +++ b/shell/common/persistent_cache.cc @@ -24,7 +24,7 @@ std::string PersistentCache::cache_base_path_; std::mutex PersistentCache::instance_mutex_; std::unique_ptr PersistentCache::gPersistentCache; -static std::string SkKeyToFilePath(const SkData& data) { +std::string PersistentCache::SkKeyToFilePath(const SkData& data) { if (data.data() == nullptr || data.size() == 0) { return ""; } diff --git a/shell/common/persistent_cache.h b/shell/common/persistent_cache.h index 35d8f08f859..c48627fdbfc 100644 --- a/shell/common/persistent_cache.h +++ b/shell/common/persistent_cache.h @@ -32,8 +32,16 @@ class PersistentCache : public GrContextOptions::PersistentCache { static PersistentCache* GetCacheForProcess(); static void ResetCacheForProcess(); + // This must be called before |GetCacheForProcess|. Otherwise, it won't + // affect the cache directory returned by |GetCacheForProcess|. static void SetCacheDirectoryPath(std::string path); + // Convert a binary SkData key into a Base32 encoded string. + // + // This is used to specify persistent cache filenames and service protocol + // json keys. + static std::string SkKeyToFilePath(const SkData& data); + ~PersistentCache() override; void AddWorkerTaskRunner(fml::RefPtr task_runner); diff --git a/shell/common/shell.cc b/shell/common/shell.cc index 8dc5cbed29f..d5132c6e4c3 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -30,6 +30,7 @@ #include "rapidjson/writer.h" #include "third_party/dart/runtime/include/dart_tools_api.h" #include "third_party/skia/include/core/SkGraphics.h" +#include "third_party/skia/include/utils/SkBase64.h" #include "third_party/tonic/common/log.h" namespace flutter { @@ -373,6 +374,10 @@ Shell::Shell(DartVMRef vm, TaskRunners task_runners, Settings settings) task_runners_.GetUITaskRunner(), std::bind(&Shell::OnServiceProtocolGetDisplayRefreshRate, this, std::placeholders::_1, std::placeholders::_2)}; + service_protocol_handlers_[ServiceProtocol::kGetSkSLsExtensionName] = { + task_runners_.GetIOTaskRunner(), + std::bind(&Shell::OnServiceProtocolGetSkSLs, this, std::placeholders::_1, + std::placeholders::_2)}; } Shell::~Shell() { @@ -1341,6 +1346,32 @@ bool Shell::OnServiceProtocolGetDisplayRefreshRate( return true; } +bool Shell::OnServiceProtocolGetSkSLs( + const ServiceProtocol::Handler::ServiceProtocolMap& params, + rapidjson::Document& response) { + FML_DCHECK(task_runners_.GetIOTaskRunner()->RunsTasksOnCurrentThread()); + response.SetObject(); + response.AddMember("type", "GetSkSLs", response.GetAllocator()); + + rapidjson::Value shaders_json(rapidjson::kObjectType); + PersistentCache* persistent_cache = PersistentCache::GetCacheForProcess(); + std::vector sksls = persistent_cache->LoadSkSLs(); + for (const auto& sksl : sksls) { + size_t b64_size = + SkBase64::Encode(sksl.second->data(), sksl.second->size(), nullptr); + sk_sp b64_data = SkData::MakeUninitialized(b64_size + 1); + char* b64_char = static_cast(b64_data->writable_data()); + SkBase64::Encode(sksl.second->data(), sksl.second->size(), b64_char); + b64_char[b64_size] = 0; // make it null terminated for printing + rapidjson::Value shader_value(b64_char, response.GetAllocator()); + rapidjson::Value shader_key(PersistentCache::SkKeyToFilePath(*sksl.first), + response.GetAllocator()); + shaders_json.AddMember(shader_key, shader_value, response.GetAllocator()); + } + response.AddMember("SkSLs", shaders_json, response.GetAllocator()); + return true; +} + // Service protocol handler bool Shell::OnServiceProtocolSetAssetBundlePath( const ServiceProtocol::Handler::ServiceProtocolMap& params, diff --git a/shell/common/shell.h b/shell/common/shell.h index d768c0673b9..199cfe1cb44 100644 --- a/shell/common/shell.h +++ b/shell/common/shell.h @@ -561,6 +561,13 @@ class Shell final : public PlatformView::Delegate, const ServiceProtocol::Handler::ServiceProtocolMap& params, rapidjson::Document& response); + // Service protocol handler + // + // The returned SkSLs are base64 encoded. Decode before storing them to files. + bool OnServiceProtocolGetSkSLs( + const ServiceProtocol::Handler::ServiceProtocolMap& params, + rapidjson::Document& response); + fml::WeakPtrFactory weak_factory_; // For accessing the Shell via the GPU thread, necessary for various diff --git a/shell/common/shell_test.cc b/shell/common/shell_test.cc index 1267876fed4..c96aece85d7 100644 --- a/shell/common/shell_test.cc +++ b/shell/common/shell_test.cc @@ -202,6 +202,20 @@ bool ShellTest::GetNeedsReportTimings(Shell* shell) { return shell->needs_report_timings_; } +void ShellTest::OnServiceProtocolGetSkSLs( + Shell* shell, + 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); + }); + finished.get_future().wait(); +} + std::shared_ptr ShellTest::GetFontCollection( Shell* shell) { return shell->weak_engine_->GetFontCollection().GetFontCollection(); diff --git a/shell/common/shell_test.h b/shell/common/shell_test.h index 9bd9f7fd396..7d35853cdbe 100644 --- a/shell/common/shell_test.h +++ b/shell/common/shell_test.h @@ -72,6 +72,14 @@ class ShellTest : public ThreadTest { static bool GetNeedsReportTimings(Shell* shell); static void SetNeedsReportTimings(Shell* shell, bool value); + // 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( + Shell* shell, + const ServiceProtocol::Handler::ServiceProtocolMap& params, + rapidjson::Document& response); + std::shared_ptr GetFontCollection(Shell* shell); // Do not assert |UnreportedTimingsCount| to be positive in any tests. diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc index 26963e1c146..a39ea3aac67 100644 --- a/shell/common/shell_unittests.cc +++ b/shell/common/shell_unittests.cc @@ -19,6 +19,7 @@ #include "flutter/fml/synchronization/count_down_latch.h" #include "flutter/fml/synchronization/waitable_event.h" #include "flutter/runtime/dart_vm.h" +#include "flutter/shell/common/persistent_cache.h" #include "flutter/shell/common/platform_view.h" #include "flutter/shell/common/rasterizer.h" #include "flutter/shell/common/shell_test.h" @@ -26,7 +27,9 @@ #include "flutter/shell/common/switches.h" #include "flutter/shell/common/thread_host.h" #include "flutter/shell/common/vsync_waiter_fallback.h" +#include "flutter/shell/version/version.h" #include "flutter/testing/testing.h" +#include "rapidjson/writer.h" #include "third_party/tonic/converter/dart_converter.h" namespace flutter { @@ -1129,5 +1132,57 @@ TEST_F(ShellTest, CanDecompressImageFromAsset) { DestroyShell(std::move(shell)); } +TEST_F(ShellTest, OnServiceProtocolGetSkSLsWorks) { + // Create 2 dummpy SkSL cache file IE (base32 encoding of A), II (base32 + // encoding of B) with content x and y. + fml::ScopedTemporaryDirectory temp_dir; + PersistentCache::SetCacheDirectoryPath(temp_dir.path()); + PersistentCache::ResetCacheForProcess(); + std::vector components = {"flutter_engine", + GetFlutterEngineVersion(), "skia", + GetSkiaVersion(), "sksl"}; + auto sksl_dir = fml::CreateDirectory(temp_dir.fd(), components, + fml::FilePermission::kReadWrite); + const std::string x = "x"; + const std::string y = "y"; + auto x_data = std::make_unique( + std::vector{x.begin(), x.end()}); + auto y_data = std::make_unique( + std::vector{y.begin(), y.end()}); + ASSERT_TRUE(fml::WriteAtomically(sksl_dir, "IE", *x_data)); + ASSERT_TRUE(fml::WriteAtomically(sksl_dir, "II", *y_data)); + + Settings settings = CreateSettingsForFixture(); + std::unique_ptr shell = CreateShell(settings); + ServiceProtocol::Handler::ServiceProtocolMap empty_params; + rapidjson::Document document; + OnServiceProtocolGetSkSLs(shell.get(), empty_params, document); + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + document.Accept(writer); + DestroyShell(std::move(shell)); + + // Base64 encoding of x, y are eQ, eA. + ASSERT_STREQ( + buffer.GetString(), + "{\"type\":\"GetSkSLs\",\"SkSLs\":{\"II\":\"eQ==\",\"IE\":\"eA==\"}}"); + + // Cleanup files + fml::FileVisitor recursive_cleanup = [&recursive_cleanup]( + const fml::UniqueFD& directory, + const std::string& filename) { + if (fml::IsDirectory(directory, filename.c_str())) { + fml::UniqueFD sub_dir = + OpenDirectoryReadOnly(directory, filename.c_str()); + VisitFiles(sub_dir, recursive_cleanup); + fml::UnlinkDirectory(directory, filename.c_str()); + } else { + fml::UnlinkFile(directory, filename.c_str()); + } + return true; + }; + VisitFiles(temp_dir.fd(), recursive_cleanup); +} + } // namespace testing } // namespace flutter