// Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include "flutter/flow/layers/container_layer.h" #include "flutter/flow/layers/layer.h" #include "flutter/flow/layers/physical_shape_layer.h" #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" #include "flutter/shell/common/switches.h" #include "flutter/testing/testing.h" #include "include/core/SkPicture.h" namespace flutter { namespace testing { static void WaitForIO(Shell* shell) { std::promise io_task_finished; shell->GetTaskRunners().GetIOTaskRunner()->PostTask( [&io_task_finished]() { io_task_finished.set_value(true); }); io_task_finished.get_future().wait(); } TEST_F(ShellTest, CacheSkSLWorks) { // Create a temp dir to store the persistent cache fml::ScopedTemporaryDirectory dir; PersistentCache::SetCacheDirectoryPath(dir.path()); PersistentCache::ResetCacheForProcess(); auto settings = CreateSettingsForFixture(); settings.cache_sksl = true; settings.dump_skp_on_shader_compilation = true; fml::AutoResetWaitableEvent firstFrameLatch; settings.frame_rasterized_callback = [&firstFrameLatch](const FrameTiming& t) { firstFrameLatch.Signal(); }; auto sksl_config = RunConfiguration::InferFromSettings(settings); sksl_config.SetEntrypoint("emptyMain"); std::unique_ptr shell = CreateShell(settings); PlatformViewNotifyCreated(shell.get()); RunEngine(shell.get(), std::move(sksl_config)); // Initially, we should have no SkSL cache auto cache = PersistentCache::GetCacheForProcess()->LoadSkSLs(); ASSERT_EQ(cache.size(), 0u); // Draw something to trigger shader compilations. LayerTreeBuilder builder = [](std::shared_ptr root) { SkPath path; path.addCircle(50, 50, 20); auto physical_shape_layer = std::make_shared( SK_ColorRED, SK_ColorBLUE, 1.0f, path, Clip::antiAlias); root->Add(physical_shape_layer); }; PumpOneFrame(shell.get(), 100, 100, builder); firstFrameLatch.Wait(); WaitForIO(shell.get()); // Some skp should be dumped due to shader compilations. int skp_count = 0; fml::FileVisitor skp_visitor = [&skp_count](const fml::UniqueFD& directory, const std::string& filename) { if (filename.size() >= 4 && filename.substr(filename.size() - 4, 4) == ".skp") { skp_count += 1; } return true; }; fml::VisitFilesRecursively(dir.fd(), skp_visitor); ASSERT_GT(skp_count, 0); // SkSL cache should be generated by the last run. cache = PersistentCache::GetCacheForProcess()->LoadSkSLs(); ASSERT_GT(cache.size(), 0u); // Run the engine again with cache_sksl = false and check that the previously // generated SkSL cache is used for precompile. PersistentCache::ResetCacheForProcess(); settings.cache_sksl = false; settings.dump_skp_on_shader_compilation = true; auto normal_config = RunConfiguration::InferFromSettings(settings); normal_config.SetEntrypoint("emptyMain"); DestroyShell(std::move(shell)); shell = CreateShell(settings); PlatformViewNotifyCreated(shell.get()); RunEngine(shell.get(), std::move(normal_config)); firstFrameLatch.Reset(); PumpOneFrame(shell.get(), 100, 100, builder); firstFrameLatch.Wait(); WaitForIO(shell.get()); // To check that all shaders are precompiled, verify that no new skp is dumped // due to shader compilations. int old_skp_count = skp_count; skp_count = 0; fml::VisitFilesRecursively(dir.fd(), skp_visitor); ASSERT_EQ(skp_count, old_skp_count); // Remove all files generated fml::FileVisitor remove_visitor = [&remove_visitor]( const fml::UniqueFD& directory, const std::string& filename) { if (fml::IsDirectory(directory, filename.c_str())) { { // To trigger fml::~UniqueFD before fml::UnlinkDirectory fml::UniqueFD sub_dir = fml::OpenDirectoryReadOnly(directory, filename.c_str()); fml::VisitFiles(sub_dir, remove_visitor); } fml::UnlinkDirectory(directory, filename.c_str()); } else { fml::UnlinkFile(directory, filename.c_str()); } return true; }; fml::VisitFiles(dir.fd(), remove_visitor); 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