| // Copyright 2020 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 <fuchsia/ui/scenic/cpp/fidl.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/sys/cpp/component_context.h> |
| |
| #include "assets/directory_asset_bundle.h" |
| #include "flutter/common/graphics/persistent_cache.h" |
| #include "flutter/fml/memory/ref_ptr.h" |
| #include "flutter/fml/message_loop_impl.h" |
| #include "flutter/fml/task_runner.h" |
| #include "flutter/shell/common/serialization_callbacks.h" |
| #include "flutter/shell/common/thread_host.h" |
| #include "flutter/shell/platform/fuchsia/flutter/gfx_session_connection.h" |
| #include "flutter/shell/platform/fuchsia/flutter/logging.h" |
| #include "flutter/shell/platform/fuchsia/flutter/runner.h" |
| #include "gtest/gtest.h" |
| #include "include/core/SkPicture.h" |
| #include "include/core/SkPictureRecorder.h" |
| #include "include/core/SkSerialProcs.h" |
| |
| using namespace flutter_runner; |
| using namespace flutter; |
| |
| namespace flutter_runner { |
| namespace testing { |
| namespace { |
| |
| std::string GetCurrentTestName() { |
| return ::testing::UnitTest::GetInstance()->current_test_info()->name(); |
| } |
| |
| } // namespace |
| |
| class MockTaskRunner : public fml::BasicTaskRunner { |
| public: |
| MockTaskRunner() {} |
| virtual ~MockTaskRunner() {} |
| |
| void PostTask(const fml::closure& task) override { |
| outstanding_tasks_.push(task); |
| } |
| |
| int GetTaskCount() { return task_count_; } |
| |
| void Run() { |
| while (!outstanding_tasks_.empty()) { |
| outstanding_tasks_.front()(); |
| outstanding_tasks_.pop(); |
| task_count_++; |
| } |
| } |
| |
| private: |
| int task_count_ = 0; |
| std::queue<const fml::closure> outstanding_tasks_; |
| }; |
| |
| class EngineTest : public ::testing::Test { |
| public: |
| void WarmupSkps( |
| uint64_t width, |
| uint64_t height, |
| std::shared_ptr<flutter::AssetManager> asset_manager, |
| std::optional<const std::vector<std::string>> skp_names, |
| std::optional<std::function<void(uint32_t)>> completion_callback) { |
| // Have to create a message loop so default async dispatcher gets set, |
| // otherwise we segfault creating the VulkanSurfaceProducer |
| auto loop = fml::MessageLoopImpl::Create(); |
| |
| context_ = sys::ComponentContext::CreateAndServeOutgoingDirectory(); |
| scenic_ = context_->svc()->Connect<fuchsia::ui::scenic::Scenic>(); |
| session_.emplace(scenic_.get()); |
| surface_producer_ = |
| std::make_unique<VulkanSurfaceProducer>(&session_.value()); |
| |
| Engine::WarmupSkps(&concurrent_task_runner_, &raster_task_runner_, |
| *surface_producer_, width, height, asset_manager, |
| std::nullopt, std::nullopt); |
| } |
| |
| protected: |
| MockTaskRunner concurrent_task_runner_; |
| MockTaskRunner raster_task_runner_; |
| std::unique_ptr<VulkanSurfaceProducer> surface_producer_; |
| |
| std::unique_ptr<sys::ComponentContext> context_; |
| fuchsia::ui::scenic::ScenicPtr scenic_; |
| std::optional<scenic::Session> session_; |
| }; |
| |
| TEST_F(EngineTest, ThreadNames) { |
| std::string prefix = GetCurrentTestName(); |
| flutter::ThreadHost engine_thread_host = Engine::CreateThreadHost(prefix); |
| |
| char thread_name[ZX_MAX_NAME_LEN]; |
| zx::thread::self()->get_property(ZX_PROP_NAME, thread_name, |
| sizeof(thread_name)); |
| EXPECT_EQ(std::string(thread_name), prefix + std::string(".platform")); |
| EXPECT_EQ(engine_thread_host.platform_thread, nullptr); |
| |
| engine_thread_host.raster_thread->GetTaskRunner()->PostTask([&prefix]() { |
| char thread_name[ZX_MAX_NAME_LEN]; |
| zx::thread::self()->get_property(ZX_PROP_NAME, thread_name, |
| sizeof(thread_name)); |
| EXPECT_EQ(std::string(thread_name), prefix + std::string(".raster")); |
| }); |
| engine_thread_host.raster_thread->Join(); |
| |
| engine_thread_host.ui_thread->GetTaskRunner()->PostTask([&prefix]() { |
| char thread_name[ZX_MAX_NAME_LEN]; |
| zx::thread::self()->get_property(ZX_PROP_NAME, thread_name, |
| sizeof(thread_name)); |
| EXPECT_EQ(std::string(thread_name), prefix + std::string(".ui")); |
| }); |
| engine_thread_host.ui_thread->Join(); |
| |
| engine_thread_host.io_thread->GetTaskRunner()->PostTask([&prefix]() { |
| char thread_name[ZX_MAX_NAME_LEN]; |
| zx::thread::self()->get_property(ZX_PROP_NAME, thread_name, |
| sizeof(thread_name)); |
| EXPECT_EQ(std::string(thread_name), prefix + std::string(".io")); |
| }); |
| engine_thread_host.io_thread->Join(); |
| } |
| |
| TEST_F(EngineTest, SkpWarmup) { |
| SkISize draw_size = SkISize::Make(100, 100); |
| SkPictureRecorder recorder; |
| auto canvas = recorder.beginRecording(draw_size.width(), draw_size.height()); |
| |
| // adapted from https://fiddle.skia.org/c/@Canvas_drawLine |
| SkPaint paint; |
| paint.setColor(0xFF9a67be); |
| paint.setStrokeWidth(20); |
| canvas->drawLine(0, 0, draw_size.width(), draw_size.height(), paint); |
| canvas->drawLine(0, draw_size.height(), draw_size.width(), 0, paint); |
| |
| sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture(); |
| SkSerialProcs procs = {0}; |
| procs.fImageProc = SerializeImageWithoutData; |
| procs.fTypefaceProc = SerializeTypefaceWithoutData; |
| sk_sp<SkData> data = picture->serialize(&procs); |
| ASSERT_TRUE(data); |
| ASSERT_GT(data->size(), 0u); |
| |
| fml::NonOwnedMapping mapping(data->bytes(), data->size()); |
| |
| fml::ScopedTemporaryDirectory asset_dir; |
| fml::UniqueFD asset_dir_fd = fml::OpenDirectory( |
| asset_dir.path().c_str(), false, fml::FilePermission::kRead); |
| fml::UniqueFD subdir_fd = fml::OpenDirectory(asset_dir_fd, "shaders", true, |
| fml::FilePermission::kReadWrite); |
| |
| bool success = fml::WriteAtomically(subdir_fd, "test.skp", mapping); |
| ASSERT_TRUE(success); |
| |
| auto asset_manager = std::make_shared<AssetManager>(); |
| asset_manager->PushBack( |
| std::make_unique<DirectoryAssetBundle>(std::move(asset_dir_fd), false)); |
| |
| WarmupSkps(draw_size.width(), draw_size.height(), asset_manager, std::nullopt, |
| std::nullopt); |
| concurrent_task_runner_.Run(); |
| raster_task_runner_.Run(); |
| |
| EXPECT_EQ(concurrent_task_runner_.GetTaskCount(), 1); |
| EXPECT_EQ(raster_task_runner_.GetTaskCount(), 1); |
| } |
| |
| TEST_F(EngineTest, SkpWarmupAsync) { |
| SkISize draw_size = SkISize::Make(100, 100); |
| SkPictureRecorder recorder; |
| auto canvas = recorder.beginRecording(draw_size.width(), draw_size.height()); |
| |
| // adapted from https://fiddle.skia.org/c/@Canvas_drawLine |
| SkPaint paint; |
| paint.setColor(0xFF9a67be); |
| paint.setStrokeWidth(20); |
| canvas->drawLine(0, 0, draw_size.width(), draw_size.height(), paint); |
| canvas->drawLine(0, draw_size.height(), draw_size.width(), 0, paint); |
| |
| sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture(); |
| SkSerialProcs procs = {0}; |
| procs.fImageProc = SerializeImageWithoutData; |
| procs.fTypefaceProc = SerializeTypefaceWithoutData; |
| sk_sp<SkData> data = picture->serialize(&procs); |
| ASSERT_TRUE(data); |
| ASSERT_GT(data->size(), 0u); |
| |
| fml::NonOwnedMapping mapping(data->bytes(), data->size()); |
| |
| fml::ScopedTemporaryDirectory asset_dir; |
| fml::UniqueFD asset_dir_fd = fml::OpenDirectory( |
| asset_dir.path().c_str(), false, fml::FilePermission::kRead); |
| fml::UniqueFD subdir_fd = fml::OpenDirectory(asset_dir_fd, "shaders", true, |
| fml::FilePermission::kReadWrite); |
| std::string skp_name = "test.skp"; |
| |
| bool success = fml::WriteAtomically(subdir_fd, skp_name.c_str(), mapping); |
| ASSERT_TRUE(success); |
| |
| auto asset_manager = std::make_shared<AssetManager>(); |
| asset_manager->PushBack( |
| std::make_unique<DirectoryAssetBundle>(std::move(asset_dir_fd), false)); |
| |
| std::vector<std::string> skp_names = {skp_name}; |
| |
| WarmupSkps(draw_size.width(), draw_size.height(), asset_manager, skp_names, |
| [](uint32_t count) { EXPECT_EQ(1u, count); }); |
| concurrent_task_runner_.Run(); |
| raster_task_runner_.Run(); |
| |
| EXPECT_EQ(concurrent_task_runner_.GetTaskCount(), 1); |
| EXPECT_EQ(raster_task_runner_.GetTaskCount(), 1); |
| } |
| |
| } // namespace testing |
| } // namespace flutter_runner |