From 5cd4605244db1fe6fab4bcc9f982a1bd3971538c Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Tue, 20 Mar 2018 18:01:01 -0700 Subject: [PATCH] Updates in flutter/content_handler for the shell refactor (Patch 3). This changes is part of a large patch for easier review. Try the whole patch in one go by checking out https://github.com/chinmaygarde/flutter_engine/tree/shell directly. This patch contains the following changes: * This patch is contains the new code added temporarily in a separate directory that contains the ApplicationRunner interface implmenetaiton on Fuchsia. This will live in the Fuchsia Topaz repository and will likely have to be reviewed separated. --- content_handler/flutter_runner/BUILD.gn | 56 ++ content_handler/flutter_runner/README.md | 4 + .../flutter_runner/accessibility_bridge.cc | 81 +++ .../flutter_runner/accessibility_bridge.h | 43 ++ content_handler/flutter_runner/application.cc | 238 ++++++++ content_handler/flutter_runner/application.h | 93 +++ .../flutter_runner/application_runner.cc | 86 +++ .../flutter_runner/application_runner.h | 79 +++ .../flutter_runner/compositor_context.cc | 86 +++ .../flutter_runner/compositor_context.h | 39 ++ content_handler/flutter_runner/engine.cc | 254 ++++++++ content_handler/flutter_runner/engine.h | 63 ++ .../flutter_runner/fuchsia_font_manager.cc | 169 ++++++ .../flutter_runner/fuchsia_font_manager.h | 89 +++ .../flutter_runner/isolate_configurator.cc | 113 ++++ .../flutter_runner/isolate_configurator.h | 54 ++ content_handler/flutter_runner/main.cc | 26 + .../flutter_runner/platform_view.cc | 550 ++++++++++++++++++ .../flutter_runner/platform_view.h | 119 ++++ .../flutter_runner/session_connection.cc | 83 +++ .../flutter_runner/session_connection.h | 62 ++ content_handler/flutter_runner/surface.cc | 73 +++ content_handler/flutter_runner/surface.h | 47 ++ .../flutter_runner/task_observers.cc | 43 ++ .../flutter_runner/task_observers.h | 16 + .../flutter_runner/unique_fdio_ns.h | 34 ++ .../flutter_runner/vulkan_surface.cc | 413 +++++++++++++ .../flutter_runner/vulkan_surface.h | 122 ++++ .../flutter_runner/vulkan_surface_pool.cc | 181 ++++++ .../flutter_runner/vulkan_surface_pool.h | 78 +++ .../flutter_runner/vulkan_surface_producer.cc | 205 +++++++ .../flutter_runner/vulkan_surface_producer.h | 77 +++ 32 files changed, 3676 insertions(+) create mode 100644 content_handler/flutter_runner/BUILD.gn create mode 100644 content_handler/flutter_runner/README.md create mode 100644 content_handler/flutter_runner/accessibility_bridge.cc create mode 100644 content_handler/flutter_runner/accessibility_bridge.h create mode 100644 content_handler/flutter_runner/application.cc create mode 100644 content_handler/flutter_runner/application.h create mode 100644 content_handler/flutter_runner/application_runner.cc create mode 100644 content_handler/flutter_runner/application_runner.h create mode 100644 content_handler/flutter_runner/compositor_context.cc create mode 100644 content_handler/flutter_runner/compositor_context.h create mode 100644 content_handler/flutter_runner/engine.cc create mode 100644 content_handler/flutter_runner/engine.h create mode 100644 content_handler/flutter_runner/fuchsia_font_manager.cc create mode 100644 content_handler/flutter_runner/fuchsia_font_manager.h create mode 100644 content_handler/flutter_runner/isolate_configurator.cc create mode 100644 content_handler/flutter_runner/isolate_configurator.h create mode 100644 content_handler/flutter_runner/main.cc create mode 100644 content_handler/flutter_runner/platform_view.cc create mode 100644 content_handler/flutter_runner/platform_view.h create mode 100644 content_handler/flutter_runner/session_connection.cc create mode 100644 content_handler/flutter_runner/session_connection.h create mode 100644 content_handler/flutter_runner/surface.cc create mode 100644 content_handler/flutter_runner/surface.h create mode 100644 content_handler/flutter_runner/task_observers.cc create mode 100644 content_handler/flutter_runner/task_observers.h create mode 100644 content_handler/flutter_runner/unique_fdio_ns.h create mode 100644 content_handler/flutter_runner/vulkan_surface.cc create mode 100644 content_handler/flutter_runner/vulkan_surface.h create mode 100644 content_handler/flutter_runner/vulkan_surface_pool.cc create mode 100644 content_handler/flutter_runner/vulkan_surface_pool.h create mode 100644 content_handler/flutter_runner/vulkan_surface_producer.cc create mode 100644 content_handler/flutter_runner/vulkan_surface_producer.h diff --git a/content_handler/flutter_runner/BUILD.gn b/content_handler/flutter_runner/BUILD.gn new file mode 100644 index 0000000000000..c4994a71bf93a --- /dev/null +++ b/content_handler/flutter_runner/BUILD.gn @@ -0,0 +1,56 @@ +# Copyright 2018 The Fuchsia Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +source_set("flutter_runner") { + sources = [ + "accessibility_bridge.cc", + "accessibility_bridge.h", + "application.cc", + "application.h", + "application_runner.cc", + "application_runner.h", + "compositor_context.cc", + "compositor_context.h", + "engine.cc", + "engine.h", + "fuchsia_font_manager.cc", + "fuchsia_font_manager.h", + "isolate_configurator.cc", + "isolate_configurator.h", + "main.cc", + "platform_view.cc", + "platform_view.h", + "session_connection.cc", + "session_connection.h", + "surface.cc", + "surface.h", + "task_observers.cc", + "task_observers.h", + "unique_fdio_ns.h", + "vulkan_surface.cc", + "vulkan_surface.h", + "vulkan_surface_pool.cc", + "vulkan_surface_pool.h", + "vulkan_surface_producer.cc", + "vulkan_surface_producer.h", + ] + + deps = [ + "$flutter_root/fml", + "$flutter_root/shell/common", + "$flutter_root/shell/gpu", + "$flutter_root/third_party/txt", + "$flutter_root/vulkan", + "//garnet/public/lib/app/cpp", + "//garnet/public/lib/fsl", + "//garnet/public/lib/ui/input/fidl", + "//garnet/public/lib/ui/views/fidl", + "//peridot/public/lib/clipboard/fidl", + "//peridot/public/lib/context/fidl", + "//third_party/skia", + "//third_party/skia:gpu", + "//third_party/zlib:minizip", + "//zircon/public/lib/trace-provider", + ] +} diff --git a/content_handler/flutter_runner/README.md b/content_handler/flutter_runner/README.md new file mode 100644 index 0000000000000..18b1586e34aa3 --- /dev/null +++ b/content_handler/flutter_runner/README.md @@ -0,0 +1,4 @@ +Flutter Application Runner +========================== + +Implements the `app::ApplicationRunner` FIDL interface to launch and run mutliple Flutter applications within the same process. diff --git a/content_handler/flutter_runner/accessibility_bridge.cc b/content_handler/flutter_runner/accessibility_bridge.cc new file mode 100644 index 0000000000000..f3d52f6cdbbb6 --- /dev/null +++ b/content_handler/flutter_runner/accessibility_bridge.cc @@ -0,0 +1,81 @@ +// Copyright 2017 The Fuchsia 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 "accessibility_bridge.h" + +#include + +#include "lib/app/cpp/application_context.h" +#include "lib/context/fidl/context_writer.fidl.h" +#include "third_party/rapidjson/rapidjson/document.h" +#include "third_party/rapidjson/rapidjson/stringbuffer.h" +#include "third_party/rapidjson/rapidjson/writer.h" + +namespace flutter { + +AccessibilityBridge::AccessibilityBridge(maxwell::ContextWriterPtr writer) + : writer_(std::move(writer)) {} + +AccessibilityBridge::~AccessibilityBridge() = default; + +void AccessibilityBridge::UpdateSemantics( + const blink::SemanticsNodeUpdates& update) { + for (const auto& update : update) { + const auto& node = update.second; + semantics_nodes_[node.id] = node; + } + std::vector visited_nodes; + UpdateVisitedForNodeAndChildren(0, &visited_nodes); + EraseUnvisitedNodes(visited_nodes); + + // The data sent to the Context Service is a JSON formatted list of labels + // for all on screen widgets. + rapidjson::Document nodes_json(rapidjson::kArrayType); + for (const int node_index : visited_nodes) { + const auto& node = semantics_nodes_[node_index]; + if (!node.label.empty()) { + rapidjson::Value value; + value.SetString(node.label.data(), node.label.size()); + nodes_json.PushBack(value, nodes_json.GetAllocator()); + } + } + + if (nodes_json.Size() > 0) { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + nodes_json.Accept(writer); + writer_->WriteEntityTopic("/inferred/accessibility_text", + buffer.GetString()); + } +} + +void AccessibilityBridge::UpdateVisitedForNodeAndChildren( + const int id, + std::vector* visited_nodes) { + std::map::const_iterator it = + semantics_nodes_.find(id); + if (it == semantics_nodes_.end()) { + return; + } + + visited_nodes->push_back(id); + for (const int child : it->second.children) { + UpdateVisitedForNodeAndChildren(child, visited_nodes); + } +} + +void AccessibilityBridge::EraseUnvisitedNodes( + const std::vector& visited_nodes) { + const std::unordered_set visited_nodes_lookup(visited_nodes.begin(), + visited_nodes.end()); + for (auto it = semantics_nodes_.begin(); it != semantics_nodes_.end();) { + if (visited_nodes_lookup.find((*it).first) == visited_nodes_lookup.end()) { + it = semantics_nodes_.erase(it); + } else { + ++it; + } + } +} + +} // namespace flutter diff --git a/content_handler/flutter_runner/accessibility_bridge.h b/content_handler/flutter_runner/accessibility_bridge.h new file mode 100644 index 0000000000000..dde8bfaa54c36 --- /dev/null +++ b/content_handler/flutter_runner/accessibility_bridge.h @@ -0,0 +1,43 @@ +// Copyright 2017 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#pragma once + +#include + +#include "flutter/lib/ui/semantics/semantics_node.h" +#include "lib/context/fidl/context_writer.fidl.h" +#include "lib/fxl/macros.h" + +namespace flutter { + +// Maintain an up-to-date list of SemanticsNodes on screen, and communicate +// with the Context Service. +class AccessibilityBridge final { + public: + AccessibilityBridge(maxwell::ContextWriterPtr writer); + + ~AccessibilityBridge(); + + // Update the internal representation of the semantics nodes, and write the + // semantics to Context Service. + void UpdateSemantics(const blink::SemanticsNodeUpdates& update); + + private: + maxwell::ContextWriterPtr writer_; + std::map semantics_nodes_; + + // Walk the semantics node tree starting at |id|, and store the id of each + // visited child in |visited_nodes|. + void UpdateVisitedForNodeAndChildren(const int id, + std::vector* visited_nodes); + + // Remove any node from |semantics_nodes_| that doesn't have an id in + // |visited_nodes|. + void EraseUnvisitedNodes(const std::vector& visited_nodes); + + FXL_DISALLOW_COPY_AND_ASSIGN(AccessibilityBridge); +}; + +} // namespace flutter diff --git a/content_handler/flutter_runner/application.cc b/content_handler/flutter_runner/application.cc new file mode 100644 index 0000000000000..5995a49fab7aa --- /dev/null +++ b/content_handler/flutter_runner/application.cc @@ -0,0 +1,238 @@ +// Copyright 2018 The Fuchsia 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 "application.h" + +#include + +#include "flutter/shell/common/switches.h" +#include "lib/fsl/vmo/vector.h" +#include "lib/fxl/command_line.h" +#include "lib/fxl/synchronization/waitable_event.h" +#include "task_observers.h" + +namespace flutter { + +std::pair, std::unique_ptr> +Application::Create( + Application::Delegate& delegate, + app::ApplicationPackagePtr package, + app::ApplicationStartupInfoPtr startup_info, + f1dl::InterfaceRequest controller) { + auto thread = std::make_unique(); + std::unique_ptr application; + + fxl::AutoResetWaitableEvent latch; + thread->TaskRunner()->PostTask([&]() mutable { + application.reset(new Application(delegate, // + std::move(package), // + std::move(startup_info), // + std::move(controller) // + )); + latch.Signal(); + }); + thread->Run(); + latch.Wait(); + return {std::move(thread), std::move(application)}; +} + +static std::string DebugLabelForURL(const std::string url) { + auto found = url.rfind("/"); + if (found == std::string::npos) { + return url; + } else { + return {url, found + 1}; + } +} + +Application::Application(Application::Delegate& delegate, + app::ApplicationPackagePtr package, + app::ApplicationStartupInfoPtr startup_info, + f1dl::InterfaceRequest + application_controller_request) + : delegate_(delegate), + debug_label_(DebugLabelForURL(startup_info->launch_info->url)), + application_controller_(this) { + application_controller_.set_error_handler([this]() { Kill(); }); + + FXL_DCHECK(fdio_ns_.is_valid()); + // ApplicationLaunchInfo::url non-optional. + auto& launch_info = startup_info->launch_info; + + // ApplicationLaunchInfo::arguments optional. + if (auto& arguments = launch_info->arguments) { + settings_ = shell::SettingsFromCommandLine( + fxl::CommandLineFromIterators(arguments.begin(), arguments.end())); + } + + // TODO: ApplicationLaunchInfo::out optional. + + // TODO: ApplicationLaunchInfo::err optional. + + // ApplicationLaunchInfo::service_request optional. + if (launch_info->service_request) { + service_provider_bridge_.ServeDirectory( + std::move(launch_info->service_request)); + } + + // ApplicationLaunchInfo::services optional. + if (launch_info->services) { + service_provider_bridge_.AddBinding(std::move(launch_info->services)); + } + + // ApplicationLaunchInfo::flat_namespace optional. + if (auto& flat_namespace = startup_info->flat_namespace) { + for (size_t i = 0; i < flat_namespace->paths.size(); ++i) { + const auto& path = flat_namespace->paths[i]; + if (path == "/svc") { + continue; + } + + zx::channel dir = std::move(flat_namespace->directories[i]); + zx_handle_t dir_handle = dir.release(); + if (fdio_ns_bind(fdio_ns_.get(), path.data(), dir_handle) != ZX_OK) { + FXL_DLOG(ERROR) << "Could not bind path to namespace: " << path; + zx_handle_close(dir_handle); + } + } + } else { + FXL_DLOG(ERROR) << "There was no flat namespace."; + } + + application_directory_.reset(fdio_ns_opendir(fdio_ns_.get())); + FXL_DCHECK(application_directory_.is_valid()); + + application_assets_directory_.reset( + openat(application_directory_.get(), "pkg/data", O_RDONLY | O_DIRECTORY)); + + // TODO: ApplicationLaunchInfo::additional_services optional. + + // ApplicationPackage::data: This is legacy FLX data. Ensure that we dont have + // any. + FXL_DCHECK(!package->data) << "Legacy FLX data must not be supplied."; + + // All launch arguments have been read. Perform service binding and + // final settings configuration. The next call will be to create a view + // for this application. + + service_provider_bridge_.AddService( + std::bind(&Application::CreateShellForView, this, std::placeholders::_1)); + + app::ServiceProviderPtr outgoing_services; + outgoing_services_request_ = outgoing_services.NewRequest(); + service_provider_bridge_.set_backend(std::move(outgoing_services)); + + // Setup the application controller binding. + if (application_controller_request) { + application_controller_.Bind(std::move(application_controller_request)); + } + + application_context_ = + app::ApplicationContext::CreateFrom(std::move(startup_info)); + + settings_.icu_data_path = ""; + + settings_.using_blink = false; + + settings_.verbose_logging = true; + + settings_.assets_dir = application_assets_directory_.get(); + + settings_.script_snapshot_path = "snapshot_blob.bin"; + + settings_.log_tag = debug_label_ + std::string{"(flutter)"}; + + settings_.task_observer_add = + std::bind(&CurrentMessageLoopAddAfterTaskObserver, std::placeholders::_1, + std::placeholders::_2); + + settings_.task_observer_remove = std::bind( + &CurrentMessageLoopRemoveAfterTaskObserver, std::placeholders::_1); +} + +Application::~Application() = default; + +// |app::ApplicationController| +void Application::Kill() { + if (last_return_code_.first) { + for (auto wait_callback : wait_callbacks_) { + wait_callback(last_return_code_.second); + } + } + wait_callbacks_.clear(); + + delegate_.OnApplicationTerminate(this); + // WARNING: Don't do anything past this point as this instance may have been + // collected. +} + +// |app::ApplicationController| +void Application::Detach() { + application_controller_.set_error_handler(nullptr); +} + +// |app::ApplicationController| +void Application::Wait(const WaitCallback& callback) { + wait_callbacks_.emplace_back(std::move(callback)); +} + +// |flutter::Engine::Delegate| +void Application::OnEngineTerminate(const Engine* shell_holder) { + auto found = std::find_if(shell_holders_.begin(), shell_holders_.end(), + [shell_holder](const auto& holder) { + return holder.get() == shell_holder; + }); + + if (found == shell_holders_.end()) { + return; + } + + // We may launch multiple shell in this application. However, we will + // terminate when the last shell goes away. The error code return to the + // application controller will be the last isolate that had an error. + auto return_code = shell_holder->GetEngineReturnCode(); + if (return_code.first) { + last_return_code_ = return_code; + } + + shell_holders_.erase(found); + + if (shell_holders_.size() == 0) { + Kill(); + // WARNING: Don't do anything past this point because the delegate may have + // collected this instance via the termination callback. + } +} + +void Application::CreateShellForView( + f1dl::InterfaceRequest view_provider_request) { + shells_bindings_.AddBinding(this, std::move(view_provider_request)); +} + +// |mozart::ViewProvider| +void Application::CreateView( + f1dl::InterfaceRequest view_owner, + f1dl::InterfaceRequest) { + if (!application_context_) { + FXL_DLOG(ERROR) << "Application context was invalid when attempting to " + "create a shell for a view provider request."; + return; + } + + // This method may be called multiple times. Care must be taken to ensure that + // all arguments can be accessed or synthesized multiple times. + // TODO(chinmaygarde): Figure out how to re-create the outgoing service + // request handle. + shell_holders_.emplace(std::make_unique( + *this, // delegate + debug_label_, // thread label + *application_context_, // application context + settings_, // settings + std::move(view_owner), // view owner + fdio_ns_, // FDIO namespace + std::move(outgoing_services_request_) // outgoing request + )); +} + +} // namespace flutter diff --git a/content_handler/flutter_runner/application.h b/content_handler/flutter_runner/application.h new file mode 100644 index 0000000000000..9484d4205eef4 --- /dev/null +++ b/content_handler/flutter_runner/application.h @@ -0,0 +1,93 @@ +// Copyright 2018 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#pragma once + +#include +#include +#include + +#include "engine.h" +#include "flutter/common/settings.h" +#include "lib/app/cpp/application_context.h" +#include "lib/app/fidl/application_controller.fidl.h" +#include "lib/fidl/cpp/bindings/binding_set.h" +#include "lib/fsl/threading/thread.h" +#include "lib/fxl/files/unique_fd.h" +#include "lib/fxl/macros.h" +#include "lib/svc/cpp/service_provider_bridge.h" +#include "lib/ui/views/fidl/view_provider.fidl.h" +#include "unique_fdio_ns.h" + +namespace flutter { + +// Represents an instance of a Flutter application that contains one of more +// Flutter engine instances. +class Application final : public Engine::Delegate, + public app::ApplicationController, + public mozart::ViewProvider { + public: + class Delegate { + public: + virtual void OnApplicationTerminate(const Application* application) = 0; + }; + + // Creates a dedicated thread to run the application and constructions the + // application on it. The application can be accessed only on this thread. + // This is a synchronous operation. + static std::pair, std::unique_ptr> + Create(Application::Delegate& delegate, + app::ApplicationPackagePtr package, + app::ApplicationStartupInfoPtr startup_info, + f1dl::InterfaceRequest controller); + + // Must be called on the same thread returned from the create call. The thread + // may be collected after. + ~Application(); + + private: + blink::Settings settings_; + Delegate& delegate_; + const std::string debug_label_; + UniqueFDIONS fdio_ns_ = UniqueFDIONSCreate(); + fxl::UniqueFD application_directory_; + fxl::UniqueFD application_assets_directory_; + f1dl::Binding application_controller_; + f1dl::InterfaceRequest outgoing_services_request_; + app::ServiceProviderBridge service_provider_bridge_; + std::unique_ptr application_context_; + f1dl::BindingSet shells_bindings_; + std::set> shell_holders_; + std::vector wait_callbacks_; + std::pair last_return_code_; + + Application(Application::Delegate& delegate, + app::ApplicationPackagePtr package, + app::ApplicationStartupInfoPtr startup_info, + f1dl::InterfaceRequest controller); + + // |app::ApplicationController| + void Kill() override; + + // |app::ApplicationController| + void Detach() override; + + // |app::ApplicationController| + void Wait(const WaitCallback& callback) override; + + // |mozart::ViewProvider| + void CreateView( + f1dl::InterfaceRequest view_owner, + f1dl::InterfaceRequest services) override; + + // |flutter::Engine::Delegate| + void OnEngineTerminate(const Engine* holder) override; + + void CreateShellForView( + f1dl::InterfaceRequest view_provider_request); + + FXL_DISALLOW_COPY_AND_ASSIGN(Application); +}; + +} // namespace flutter diff --git a/content_handler/flutter_runner/application_runner.cc b/content_handler/flutter_runner/application_runner.cc new file mode 100644 index 0000000000000..82887106b868a --- /dev/null +++ b/content_handler/flutter_runner/application_runner.cc @@ -0,0 +1,86 @@ +// Copyright 2018 The Fuchsia 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 "application_runner.h" + +#include + +#include "flutter/lib/ui/text/font_collection.h" +#include "fuchsia_font_manager.h" +#include "lib/fonts/fidl/font_provider.fidl.h" +#include "lib/icu_data/cpp/icu_data.h" + +namespace flutter { + +ApplicationRunner::ApplicationRunner(fxl::Closure on_termination_callback) + : on_termination_callback_(std::move(on_termination_callback)), + host_context_(app::ApplicationContext::CreateFromStartupInfo()) { + SetupICU(); + + SetupGlobalFonts(); + + const std::string process_label = "flutter"; + zx::process::self().set_property(ZX_PROP_NAME, process_label.c_str(), + process_label.size()); + + host_context_->outgoing_services()->AddService( + std::bind(&ApplicationRunner::RegisterApplication, this, + std::placeholders::_1)); + + active_applications_bindings_.set_empty_set_handler( + [this]() { FireTerminationCallbackIfNecessary(); }); +} + +ApplicationRunner::~ApplicationRunner() { + host_context_->outgoing_services()->RemoveService(); +} + +void ApplicationRunner::RegisterApplication( + f1dl::InterfaceRequest request) { + active_applications_bindings_.AddBinding(this, std::move(request)); +} + +void ApplicationRunner::StartApplication( + app::ApplicationPackagePtr package, + app::ApplicationStartupInfoPtr startup_info, + f1dl::InterfaceRequest controller) { + auto thread_application_pair = + Application::Create(*this, // delegate + std::move(package), // application pacakge + std::move(startup_info), // startup info + std::move(controller) // controller request + ); + active_applications_[thread_application_pair.second.get()] = + std::move(thread_application_pair); +} + +void ApplicationRunner::OnApplicationTerminate(const Application* application) { + active_applications_.erase(application); + FireTerminationCallbackIfNecessary(); +} + +void ApplicationRunner::SetupICU() { + if (!icu_data::Initialize(host_context_.get())) { + FXL_LOG(ERROR) << "Could not initialize ICU data."; + } +} + +void ApplicationRunner::SetupGlobalFonts() { + fonts::FontProviderPtr font_provider( + host_context_->ConnectToEnvironmentService()); + blink::FontCollection::ForProcess().GetFontCollection()->PushFront( + sk_make_sp(std::move(font_provider))); +} + +void ApplicationRunner::FireTerminationCallbackIfNecessary() { + // We have no reason to exist if: + // 1: No previously launched applications are running. + // 2: No bindings exist that may require launching more applications. + if (on_termination_callback_ && active_applications_.size() == 0 && + active_applications_bindings_.size() == 0) { + on_termination_callback_(); + } +} + +} // namespace flutter diff --git a/content_handler/flutter_runner/application_runner.h b/content_handler/flutter_runner/application_runner.h new file mode 100644 index 0000000000000..924f86bfec967 --- /dev/null +++ b/content_handler/flutter_runner/application_runner.h @@ -0,0 +1,79 @@ +// Copyright 2018 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#pragma once + +#include +#include + +#include "application.h" +#include "lib/app/cpp/application_context.h" +#include "lib/app/fidl/application_runner.fidl.h" +#include "lib/fidl/cpp/bindings/binding_set.h" +#include "lib/fsl/tasks/message_loop.h" +#include "lib/fxl/functional/make_copyable.h" +#include "lib/fxl/macros.h" + +namespace flutter { + +// Publishes the |app::ApplicationRunner| service and runs applications on their +// own threads. +class ApplicationRunner final : public Application::Delegate, + public app::ApplicationRunner { + public: + ApplicationRunner(fxl::Closure on_termination_callback); + + ~ApplicationRunner(); + + private: + struct ActiveApplication { + std::unique_ptr thread; + std::unique_ptr application; + + ActiveApplication(std::pair, + std::unique_ptr> pair) + : thread(std::move(pair.first)), application(std::move(pair.second)) {} + + ActiveApplication() { + if (thread && application) { + thread->TaskRunner()->PostTask( + fxl::MakeCopyable([application = std::move(application)]() mutable { + application.reset(); + fsl::MessageLoop::GetCurrent()->PostQuitTask(); + })); + thread.reset(); // join + } + } + }; + + fxl::Closure on_termination_callback_; + std::unique_ptr host_context_; + f1dl::BindingSet active_applications_bindings_; + std::unordered_map + active_applications_; + + // |app::ApplicationRunner| + void StartApplication( + app::ApplicationPackagePtr application, + app::ApplicationStartupInfoPtr startup_info, + f1dl::InterfaceRequest controller) override; + + void RegisterApplication( + f1dl::InterfaceRequest request); + + void UnregisterApplication(const Application* application); + + // |Application::Delegate| + void OnApplicationTerminate(const Application* application) override; + + void SetupICU(); + + void SetupGlobalFonts(); + + void FireTerminationCallbackIfNecessary(); + + FXL_DISALLOW_COPY_AND_ASSIGN(ApplicationRunner); +}; + +} // namespace flutter diff --git a/content_handler/flutter_runner/compositor_context.cc b/content_handler/flutter_runner/compositor_context.cc new file mode 100644 index 0000000000000..30826b88c4f81 --- /dev/null +++ b/content_handler/flutter_runner/compositor_context.cc @@ -0,0 +1,86 @@ +// Copyright 2018 The Fuchsia 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 "compositor_context.h" + +#include "flutter/flow/layers/layer_tree.h" +#include "flutter/glue/trace_event.h" + +namespace flutter { + +class ScopedFrame final : public flow::CompositorContext::ScopedFrame { + public: + ScopedFrame(flow::CompositorContext& context, + bool instrumentation_enabled, + SessionConnection& session_connection) + : flow::CompositorContext::ScopedFrame(context, + nullptr, + nullptr, + instrumentation_enabled), + session_connection_(session_connection) {} + + private: + SessionConnection& session_connection_; + + bool Raster(flow::LayerTree& layer_tree, bool ignore_raster_cache) override { + if (!session_connection_.has_metrics()) { + return true; + } + + { + // Preroll the Flutter layer tree. This allows Flutter to perform + // pre-paint optimizations. + TRACE_EVENT0("flutter", "Preroll"); + layer_tree.Preroll(*this, true /* ignore raster cache */); + } + + { + // Traverse the Flutter layer tree so that the necessary session ops to + // represent the frame are enqueued in the underlying session. + TRACE_EVENT0("flutter", "UpdateScene"); + layer_tree.UpdateScene(session_connection_.scene_update_context(), + session_connection_.root_node()); + } + + { + // Flush all pending session ops. + TRACE_EVENT0("flutter", "SessionPresent"); + session_connection_.Present(*this); + } + + return true; + } + + FXL_DISALLOW_COPY_AND_ASSIGN(ScopedFrame); +}; + +CompositorContext::CompositorContext( + const ui_mozart::MozartPtr& mozart, + std::string debug_label, + zx::eventpair import_token, + OnMetricsUpdate session_metrics_did_change_callback, + fxl::Closure session_error_callback) + : debug_label_(std::move(debug_label)), + session_connection_(mozart, + debug_label_, + std::move(import_token), + std::move(session_metrics_did_change_callback), + std::move(session_error_callback)) {} + +CompositorContext::~CompositorContext() = default; + +std::unique_ptr +CompositorContext::AcquireFrame(GrContext* gr_context, + SkCanvas* canvas, + bool instrumentation_enabled) { + // TODO: The AcquireFrame interface is too broad and must be refactored to get + // rid of the context and canvas arguments as those seem to be only used for + // colorspace correctness purposes on the mobile shells. + return std::make_unique(*this, // + instrumentation_enabled, // + session_connection_ // + ); +} + +} // namespace flutter diff --git a/content_handler/flutter_runner/compositor_context.h b/content_handler/flutter_runner/compositor_context.h new file mode 100644 index 0000000000000..49c0c832da086 --- /dev/null +++ b/content_handler/flutter_runner/compositor_context.h @@ -0,0 +1,39 @@ +// Copyright 2018 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#pragma once + +#include "flutter/flow/compositor_context.h" +#include "lib/fxl/macros.h" +#include "lib/ui/scenic/fidl/scene_manager.fidl.h" +#include "session_connection.h" + +namespace flutter { + +// Holds composition specific state and bindings specific to composition on +// Fuchsia. +class CompositorContext final : public flow::CompositorContext { + public: + CompositorContext(const ui_mozart::MozartPtr& mozart, + std::string debug_label, + zx::eventpair import_token, + OnMetricsUpdate session_metrics_did_change_callback, + fxl::Closure session_error_callback); + + ~CompositorContext() override; + + private: + const std::string debug_label_; + SessionConnection session_connection_; + + // |flow::CompositorContext| + std::unique_ptr AcquireFrame( + GrContext* gr_context, + SkCanvas* canvas, + bool instrumentation_enabled) override; + + FXL_DISALLOW_COPY_AND_ASSIGN(CompositorContext); +}; + +} // namespace flutter diff --git a/content_handler/flutter_runner/engine.cc b/content_handler/flutter_runner/engine.cc new file mode 100644 index 0000000000000..8249b650e8b35 --- /dev/null +++ b/content_handler/flutter_runner/engine.cc @@ -0,0 +1,254 @@ +// Copyright 2018 The Fuchsia 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 "engine.h" + +#include + +#include "flutter/common/task_runners.h" +#include "flutter/fml/task_runner.h" +#include "flutter/shell/common/rasterizer.h" +#include "flutter/shell/common/run_configuration.h" +#include "lib/fsl/tasks/message_loop.h" +#include "lib/fxl/functional/make_copyable.h" +#include "lib/fxl/synchronization/waitable_event.h" +#include "platform_view.h" + +namespace flutter { + +Engine::Engine( + Delegate& delegate, + std::string thread_label, + app::ApplicationContext& application_context, + blink::Settings settings, + f1dl::InterfaceRequest view_owner, + const UniqueFDIONS& fdio_ns, + f1dl::InterfaceRequest outgoing_services_request) + : delegate_(delegate), + thread_label_(std::move(thread_label)), + settings_(std::move(settings)), + weak_factory_(this) { + // Launch the threads that will be used to run the shell. These threads will + // be joined in the destructor. + for (auto& thread : host_threads_) { + thread.Run(); + } + + mozart::ViewManagerPtr view_manager; + application_context.ConnectToEnvironmentService(view_manager.NewRequest()); + + zx::eventpair import_token, export_token; + if (zx::eventpair::create(0u, &import_token, &export_token) != ZX_OK) { + FXL_DLOG(ERROR) << "Could not create event pair."; + return; + } + + // Setup the session connection. + ui_mozart::MozartPtr mozart; + view_manager->GetMozart(mozart.NewRequest()); + + // Grab the parent environent services. The platform view may want to access + // some of these services. + app::ServiceProviderPtr parent_environment_service_provider; + application_context.environment()->GetServices( + parent_environment_service_provider.NewRequest()); + + // We need to manually schedule a frame when the session metrics change. + OnMetricsUpdate on_session_metrics_change_callback = std::bind( + &Engine::OnSessionMetricsDidChange, this, std::placeholders::_1); + + fxl::Closure on_session_error_callback = std::bind(&Engine::Terminate, this); + + // Grab the accessibilty context writer that can understand the semtics tree + // on the platform view. + maxwell::ContextWriterPtr accessibility_context_writer; + application_context.ConnectToEnvironmentService( + accessibility_context_writer.NewRequest()); + + // Setup the callback that will instantiate the platform view. + shell::Shell::CreateCallback on_create_platform_view = + fxl::MakeCopyable([debug_label = thread_label_, // + parent_environment_service_provider = + std::move(parent_environment_service_provider), // + view_manager = std::ref(view_manager), // + view_owner = std::move(view_owner), // + mozart = std::move(mozart), // + accessibility_context_writer = + std::move(accessibility_context_writer), // + export_token = std::move(export_token), // + import_token = std::move(import_token), // + on_session_metrics_change_callback, // + on_session_error_callback // + ](shell::Shell& shell) mutable { + return std::make_unique( + shell, // delegate + debug_label, // debug label + shell.GetTaskRunners(), // task runners + std::move(parent_environment_service_provider), // services + view_manager, // view manager + std::move(view_owner), // view owner + std::move(mozart), // mozart + std::move(export_token), // export token + std::move(import_token), // import token + std::move( + accessibility_context_writer), // accessibility context writer + std::move(on_session_metrics_change_callback), // metrics change + std::move(on_session_error_callback) // session_error + ); + }); + + // Setup the callback that will instantiate the rasterizer. + shell::Shell::CreateCallback on_create_rasterizer = + [](shell::Shell& shell) { + return std::make_unique( + shell.GetTaskRunners() // task runners + ); + }; + + // Get the task runners from the managed threads. The current thread will be + // used as the "platform" thread. + blink::TaskRunners task_runners( + thread_label_, // Dart thread labels + fsl::MessageLoop::GetCurrent()->task_runner(), // platform + host_threads_[0].TaskRunner(), // gpu + host_threads_[1].TaskRunner(), // ui + host_threads_[2].TaskRunner() // io + ); + + settings_.root_isolate_create_callback = + std::bind(&Engine::OnMainIsolateStart, this); + + settings_.root_isolate_shutdown_callback = + std::bind([weak = weak_factory_.GetWeakPtr(), + runner = task_runners.GetPlatformTaskRunner()]() { + runner->PostTask([weak = std::move(weak)] { + if (weak) { + weak->OnMainIsolateShutdown(); + } + }); + }); + + shell_ = shell::Shell::Create( + task_runners, // host task runners + settings_, // shell launch settings + on_create_platform_view, // platform view create callback + on_create_rasterizer // rasterizer create callback + ); + + if (!shell_) { + FXL_LOG(ERROR) << "Could not launch the shell with settings: " + << settings_.ToString(); + return; + } + + // Shell has been created. Before we run the engine, setup the isolate + // configurator. + { + PlatformView* platform_view = + static_cast(shell_->GetPlatformView().get()); + auto& view = platform_view->GetMozartView(); + app::ApplicationEnvironmentPtr application_environment; + application_context.ConnectToEnvironmentService( + application_environment.NewRequest()); + + isolate_configurator_ = std::make_unique( + fdio_ns, // + view, // + std::move(application_environment), // + std::move(outgoing_services_request) // + ); + } + + // This platform does not get a separate surface platform view creation + // notification. Fire one eagerly. + shell_->GetPlatformView()->NotifyCreated(); + + // Launch the engine in the appropriate configuration. + auto run_configuration = + shell::RunConfiguration::DefaultRunConfigurationFromSettings(settings_); + + shell_->GetTaskRunners().GetUITaskRunner()->PostTask( + fxl::MakeCopyable([engine = shell_->GetEngine(), // + run_configuration = std::move(run_configuration) // + ]() mutable { + if (!engine || !engine->Run(std::move(run_configuration))) { + FXL_LOG(ERROR) << "Could not (re)launch the engine in configuration"; + } + })); + + UpdateNativeThreadLabelNames(); +} + +Engine::~Engine() { + for (const auto& thread : host_threads_) { + thread.TaskRunner()->PostTask( + []() { fsl::MessageLoop::GetCurrent()->PostQuitTask(); }); + } +} + +void Engine::UpdateNativeThreadLabelNames() const { + auto set_thread_name = [](fxl::RefPtr runner, + std::string prefix, std::string suffix) { + runner->PostTask([name = prefix + suffix]() { + zx::thread::self().set_property(ZX_PROP_NAME, name.c_str(), name.size()); + }); + }; + auto runners = shell_->GetTaskRunners(); + set_thread_name(runners.GetPlatformTaskRunner(), thread_label_, ".platform"); + set_thread_name(runners.GetUITaskRunner(), thread_label_, ".ui"); + set_thread_name(runners.GetGPUTaskRunner(), thread_label_, ".gpu"); + set_thread_name(runners.GetIOTaskRunner(), thread_label_, ".io"); +} + +std::pair Engine::GetEngineReturnCode() const { + std::pair code(false, 0); + if (!shell_) { + return code; + } + fxl::AutoResetWaitableEvent latch; + fml::TaskRunner::RunNowOrPostTask( + shell_->GetTaskRunners().GetUITaskRunner(), + [&latch, &code, engine = shell_->GetEngine()]() { + if (engine) { + code = engine->GetUIIsolateReturnCode(); + } + latch.Signal(); + }); + latch.Wait(); + return code; +} + +void Engine::OnMainIsolateStart() { + if (!isolate_configurator_ || + !isolate_configurator_->ConfigureCurrentIsolate()) { + FXL_LOG(ERROR) << "Could not configure some native embedder bindings for a " + "new root isolate."; + } +} + +void Engine::OnMainIsolateShutdown() { + Terminate(); +} + +void Engine::Terminate() { + delegate_.OnEngineTerminate(this); + // Warning. Do not do anything after this point as the delegate may have + // collected this object. +} + +void Engine::OnSessionMetricsDidChange(double device_pixel_ratio) { + if (!shell_) { + return; + } + + shell_->GetTaskRunners().GetPlatformTaskRunner()->PostTask( + [platform_view = shell_->GetPlatformView(), device_pixel_ratio]() { + if (platform_view) { + reinterpret_cast(platform_view.get()) + ->UpdateViewportMetrics(device_pixel_ratio); + } + }); +} + +} // namespace flutter diff --git a/content_handler/flutter_runner/engine.h b/content_handler/flutter_runner/engine.h new file mode 100644 index 0000000000000..76c0dafa76fcc --- /dev/null +++ b/content_handler/flutter_runner/engine.h @@ -0,0 +1,63 @@ +// Copyright 2018 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#pragma once + +#include "flutter/shell/common/shell.h" +#include "isolate_configurator.h" +#include "lib/app/cpp/application_context.h" +#include "lib/fsl/threading/thread.h" +#include "lib/fxl/macros.h" +#include "lib/fxl/memory/weak_ptr.h" +#include "lib/ui/views/fidl/view_manager.fidl.h" + +namespace flutter { + +// Represents an instance of running Flutter engine along with the threads that +// host the same. +class Engine final { + public: + class Delegate { + public: + virtual void OnEngineTerminate(const Engine* holder) = 0; + }; + + Engine( + Delegate& delegate, + std::string thread_label, + app::ApplicationContext& application_context, + blink::Settings settings, + f1dl::InterfaceRequest view_owner, + const UniqueFDIONS& fdio_ns, + f1dl::InterfaceRequest outgoing_services_request); + + ~Engine(); + + // Returns the Dart return code for the root isolate if one is present. This + // call is thread safe and synchronous. This call must be made infrequently. + std::pair GetEngineReturnCode() const; + + private: + Delegate& delegate_; + const std::string thread_label_; + blink::Settings settings_; + std::array host_threads_; + std::unique_ptr isolate_configurator_; + std::unique_ptr shell_; + fxl::WeakPtrFactory weak_factory_; + + void OnMainIsolateStart(); + + void OnMainIsolateShutdown(); + + void Terminate(); + + void OnSessionMetricsDidChange(double device_pixel_ratio); + + void UpdateNativeThreadLabelNames() const; + + FXL_DISALLOW_COPY_AND_ASSIGN(Engine); +}; + +} // namespace flutter diff --git a/content_handler/flutter_runner/fuchsia_font_manager.cc b/content_handler/flutter_runner/fuchsia_font_manager.cc new file mode 100644 index 0000000000000..877c931130eb4 --- /dev/null +++ b/content_handler/flutter_runner/fuchsia_font_manager.cc @@ -0,0 +1,169 @@ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "fuchsia_font_manager.h" + +#include + +#include "lib/fsl/vmo/sized_vmo.h" +#include "lib/fxl/logging.h" +#include "txt/asset_font_style_set.h" + +namespace txt { + +namespace { + +void UnmapMemory(const void* buffer, void* context) { + static_assert(sizeof(void*) == sizeof(uint64_t), "pointers aren't 64-bit"); + const uint64_t size = reinterpret_cast(context); + zx::vmar::root_self().unmap(reinterpret_cast(buffer), size); +} + +sk_sp MakeSkDataFromVMO(const fsl::SizedVmoTransportPtr& vmo) { + if (!fsl::SizedVmo::IsSizeValid(vmo->vmo, vmo->size) || + vmo->size > std::numeric_limits::max()) { + return nullptr; + } + uint64_t size = vmo->size; + uintptr_t buffer = 0; + zx_status_t status = zx::vmar::root_self().map(0, vmo->vmo, 0, size, + ZX_VM_FLAG_PERM_READ, &buffer); + if (status != ZX_OK) + return nullptr; + return SkData::MakeWithProc(reinterpret_cast(buffer), size, + UnmapMemory, reinterpret_cast(size)); +} + +fonts::FontSlant ToFontSlant(SkFontStyle::Slant slant) { + return (slant == SkFontStyle::kItalic_Slant) ? fonts::FontSlant::ITALIC + : fonts::FontSlant::UPRIGHT; +} + +} // anonymous namespace + +FuchsiaFontManager::FuchsiaFontManager(fonts::FontProviderPtr provider) + : font_provider_(std::move(provider)) {} + +FuchsiaFontManager::~FuchsiaFontManager() = default; + +int FuchsiaFontManager::onCountFamilies() const { + FXL_DCHECK(false); + return 0; +} + +void FuchsiaFontManager::onGetFamilyName(int index, + SkString* familyName) const { + FXL_DCHECK(false); +} + +SkFontStyleSet* FuchsiaFontManager::onCreateStyleSet(int index) const { + FXL_DCHECK(false); + return nullptr; +} + +SkFontStyleSet* FuchsiaFontManager::onMatchFamily( + const char family_name[]) const { + sk_sp typeface(onMatchFamilyStyle(family_name, SkFontStyle())); + if (!typeface) + return nullptr; + + sk_sp font_style_set( + sk_make_sp()); + font_style_set->registerTypeface(typeface); + + return font_style_set.release(); +} + +SkTypeface* FuchsiaFontManager::onMatchFamilyStyle( + const char family_name[], + const SkFontStyle& style) const { + auto request = fonts::FontRequest::New(); + request->family = family_name; + request->weight = style.weight(); + request->width = style.width(); + request->slant = ToFontSlant(style.slant()); + + fonts::FontResponsePtr response; + font_provider_->GetFont( + std::move(request), + [&response](fonts::FontResponsePtr r) { response = std::move(r); }); + font_provider_.WaitForResponse(); + + if (!response) { + FXL_DLOG(ERROR) << "Unable to contact the font provider. Did you run " + "Flutter in an environment that has a font manager?"; + return nullptr; + } + + sk_sp data = MakeSkDataFromVMO(response->data->vmo); + if (!data) + return nullptr; + + sk_sp typeface = + SkFontMgr::RefDefault()->makeFromData(std::move(data)); + + return typeface.release(); +} + +SkTypeface* FuchsiaFontManager::onMatchFamilyStyleCharacter( + const char familyName[], + const SkFontStyle&, + const char* bcp47[], + int bcp47Count, + SkUnichar character) const { + return nullptr; +} + +SkTypeface* FuchsiaFontManager::onMatchFaceStyle(const SkTypeface*, + const SkFontStyle&) const { + FXL_DCHECK(false); + return nullptr; +} + +sk_sp FuchsiaFontManager::onMakeFromData(sk_sp, + int ttcIndex) const { + FXL_DCHECK(false); + return nullptr; +} + +sk_sp FuchsiaFontManager::onMakeFromStreamIndex( + std::unique_ptr, + int ttcIndex) const { + FXL_DCHECK(false); + return nullptr; +} + +sk_sp FuchsiaFontManager::onMakeFromStreamArgs( + std::unique_ptr, + const SkFontArguments&) const { + FXL_DCHECK(false); + return nullptr; +} + +sk_sp FuchsiaFontManager::onMakeFromFile(const char path[], + int ttcIndex) const { + FXL_DCHECK(false); + return nullptr; +} + +sk_sp FuchsiaFontManager::onLegacyMakeTypeface( + const char familyName[], + SkFontStyle) const { + FXL_DCHECK(false); + return nullptr; +} + +} // namespace txt diff --git a/content_handler/flutter_runner/fuchsia_font_manager.h b/content_handler/flutter_runner/fuchsia_font_manager.h new file mode 100644 index 0000000000000..72c55374178a4 --- /dev/null +++ b/content_handler/flutter_runner/fuchsia_font_manager.h @@ -0,0 +1,89 @@ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TXT_FUCHSIA_FONT_MANAGER_H_ +#define TXT_FUCHSIA_FONT_MANAGER_H_ + +#include +#include "lib/fonts/fidl/font_provider.fidl.h" +#include "lib/fxl/macros.h" +#include "third_party/skia/include/core/SkStream.h" +#include "third_party/skia/include/core/SkTypeface.h" +#include "third_party/skia/include/ports/SkFontMgr.h" + +namespace txt { + +class FuchsiaFontManager final : public SkFontMgr { + public: + FuchsiaFontManager(fonts::FontProviderPtr provider); + + ~FuchsiaFontManager() override; + + protected: + // |SkFontMgr| + int onCountFamilies() const override; + + // |SkFontMgr| + void onGetFamilyName(int index, SkString* familyName) const override; + + // |SkFontMgr| + SkFontStyleSet* onMatchFamily(const char familyName[]) const override; + + // |SkFontMgr| + SkFontStyleSet* onCreateStyleSet(int index) const override; + + // |SkFontMgr| + SkTypeface* onMatchFamilyStyle(const char familyName[], + const SkFontStyle&) const override; + + // |SkFontMgr| + SkTypeface* onMatchFamilyStyleCharacter(const char familyName[], + const SkFontStyle&, + const char* bcp47[], + int bcp47Count, + SkUnichar character) const override; + // |SkFontMgr| + SkTypeface* onMatchFaceStyle(const SkTypeface*, + const SkFontStyle&) const override; + + // |SkFontMgr| + sk_sp onMakeFromData(sk_sp, int ttcIndex) const override; + + // |SkFontMgr| + sk_sp onMakeFromStreamIndex(std::unique_ptr, + int ttcIndex) const override; + + // |SkFontMgr| + sk_sp onMakeFromStreamArgs(std::unique_ptr, + const SkFontArguments&) const override; + + // |SkFontMgr| + sk_sp onMakeFromFile(const char path[], + int ttcIndex) const override; + + // |SkFontMgr| + sk_sp onLegacyMakeTypeface(const char familyName[], + SkFontStyle) const override; + + FXL_DISALLOW_COPY_AND_ASSIGN(FuchsiaFontManager); + + private: + mutable fonts::FontProviderPtr font_provider_; +}; + +} // namespace txt + +#endif // TXT_FUCHSIA_FONT_MANAGER_H_ diff --git a/content_handler/flutter_runner/isolate_configurator.cc b/content_handler/flutter_runner/isolate_configurator.cc new file mode 100644 index 0000000000000..aeb6fab0cabfa --- /dev/null +++ b/content_handler/flutter_runner/isolate_configurator.cc @@ -0,0 +1,113 @@ +// Copyright 2018 The Fuchsia 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 "isolate_configurator.h" + +#include "dart-pkg/fuchsia/sdk_ext/fuchsia.h" +#include "dart-pkg/zircon/sdk_ext/handle.h" +#include "lib/tonic/converter/dart_converter.h" +#include "lib/tonic/dart_state.h" +#include "lib/tonic/logging/dart_error.h" +#include "lib/ui/flutter/sdk_ext/src/natives.h" +#include "third_party/dart/runtime/include/dart_api.h" + +namespace flutter { + +IsolateConfigurator::IsolateConfigurator( + const UniqueFDIONS& fdio_ns, + mozart::ViewPtr& view, + app::ApplicationEnvironmentPtr application_environment, + f1dl::InterfaceRequest outgoing_services_request) + : fdio_ns_(fdio_ns), + view_(view), + application_environment_(std::move(application_environment)), + outgoing_services_request_(std::move(outgoing_services_request)) {} + +IsolateConfigurator::~IsolateConfigurator() = default; + +bool IsolateConfigurator::ConfigureCurrentIsolate() { + if (used_) { + return false; + } + used_ = true; + + BindFuchsia(); + BindZircon(); + BindDartIO(); + BindMozart(); + + return true; +} + +// |mozart::NativesDelegate| +mozart::View* IsolateConfigurator::GetMozartView() { + return view_.get(); +} + +void IsolateConfigurator::BindFuchsia() { + fuchsia::dart::Initialize(application_environment_.Unbind(), + std::move(outgoing_services_request_)); +} + +void IsolateConfigurator::BindZircon() { + // Tell dart:zircon about the FDIO namespace configured for this instance. + Dart_Handle zircon_lib = Dart_LookupLibrary(tonic::ToDart("dart:zircon")); + DART_CHECK_VALID(zircon_lib); + + Dart_Handle namespace_type = + Dart_GetType(zircon_lib, tonic::ToDart("_Namespace"), 0, nullptr); + DART_CHECK_VALID(namespace_type); + DART_CHECK_VALID( + Dart_SetField(namespace_type, // + tonic::ToDart("_namespace"), // + tonic::ToDart(reinterpret_cast(fdio_ns_.get())))); +} + +void IsolateConfigurator::BindDartIO() { + // Grab the dart:io lib. + Dart_Handle io_lib = Dart_LookupLibrary(tonic::ToDart("dart:io")); + DART_CHECK_VALID(io_lib); + + // Disable dart:io exit() + Dart_Handle embedder_config_type = + Dart_GetType(io_lib, tonic::ToDart("_EmbedderConfig"), 0, nullptr); + DART_CHECK_VALID(embedder_config_type); + DART_CHECK_VALID(Dart_SetField(embedder_config_type, + tonic::ToDart("_mayExit"), Dart_False())); + + // Tell dart:io about the FDIO namespace configured for this instance. + Dart_Handle namespace_type = + Dart_GetType(io_lib, tonic::ToDart("_Namespace"), 0, nullptr); + DART_CHECK_VALID(namespace_type); + Dart_Handle namespace_args[] = { + Dart_NewInteger(reinterpret_cast(fdio_ns_.get())), // + }; + DART_CHECK_VALID(namespace_args[0]); + DART_CHECK_VALID(Dart_Invoke(namespace_type, tonic::ToDart("_setupNamespace"), + 1, namespace_args)); +} + +void IsolateConfigurator::BindMozart() { + Dart_Handle mozart_internal = + Dart_LookupLibrary(tonic::ToDart("dart:mozart.internal")); + DART_CHECK_VALID(mozart_internal); + DART_CHECK_VALID(Dart_SetNativeResolver(mozart_internal, // + mozart::NativeLookup, // + mozart::NativeSymbol) // + ); + DART_CHECK_VALID(Dart_SetField( + mozart_internal, // + tonic::ToDart("_context"), // + tonic::DartConverter::ToDart(reinterpret_cast( + static_cast(this))))); + mozart::ViewContainerPtr view_container; + view_->GetContainer(view_container.NewRequest()); + DART_CHECK_VALID( + Dart_SetField(mozart_internal, // + tonic::ToDart("_viewContainer"), // + tonic::ToDart(zircon::dart::Handle::Create( + view_container.Unbind().TakeChannel().release())))); +} + +} // namespace flutter diff --git a/content_handler/flutter_runner/isolate_configurator.h b/content_handler/flutter_runner/isolate_configurator.h new file mode 100644 index 0000000000000..2d8a731d85c9f --- /dev/null +++ b/content_handler/flutter_runner/isolate_configurator.h @@ -0,0 +1,54 @@ +// Copyright 2018 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#pragma once + +#include "lib/app/fidl/application_environment.fidl.h" +#include "lib/fxl/macros.h" +#include "lib/ui/flutter/sdk_ext/src/natives.h" +#include "lib/ui/views/fidl/view_containers.fidl.h" +#include "lib/ui/views/fidl/views.fidl.h" +#include "unique_fdio_ns.h" + +namespace flutter { + +// Contains all the information necessary to configure a new root isolate. This +// is a single use item. The lifetime of this object must extend past that of +// the root isolate. +class IsolateConfigurator final : mozart::NativesDelegate { + public: + IsolateConfigurator( + const UniqueFDIONS& fdio_ns, + mozart::ViewPtr& view, + app::ApplicationEnvironmentPtr application_environment, + f1dl::InterfaceRequest outgoing_services_request); + + ~IsolateConfigurator(); + + // Can be used only once and only on the UI thread with the newly created + // isolate already current. + bool ConfigureCurrentIsolate(); + + private: + bool used_ = false; + const UniqueFDIONS& fdio_ns_; + mozart::ViewPtr& view_; + app::ApplicationEnvironmentPtr application_environment_; + f1dl::InterfaceRequest outgoing_services_request_; + + // |mozart::NativesDelegate| + mozart::View* GetMozartView() override; + + void BindFuchsia(); + + void BindZircon(); + + void BindDartIO(); + + void BindMozart(); + + FXL_DISALLOW_COPY_AND_ASSIGN(IsolateConfigurator); +}; + +} // namespace flutter diff --git a/content_handler/flutter_runner/main.cc b/content_handler/flutter_runner/main.cc new file mode 100644 index 0000000000000..15b8cede5b9b2 --- /dev/null +++ b/content_handler/flutter_runner/main.cc @@ -0,0 +1,26 @@ +// Copyright 2018 The Fuchsia 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 + +#include "application_runner.h" +#include "lib/fsl/tasks/message_loop.h" + +int main(int argc, char const* argv[]) { + fsl::MessageLoop loop; + + trace::TraceProvider provider(loop.async()); + FXL_DCHECK(provider.is_valid()) << "Trace provider must be valid."; + + FXL_LOG(INFO) << "Flutter application services initialized."; + flutter::ApplicationRunner runner([&loop]() { + loop.PostQuitTask(); + FXL_LOG(INFO) << "Flutter application services terminated. Good bye..."; + }); + + loop.Run(); + + return EXIT_SUCCESS; +} diff --git a/content_handler/flutter_runner/platform_view.cc b/content_handler/flutter_runner/platform_view.cc new file mode 100644 index 0000000000000..411be4c1a7b0b --- /dev/null +++ b/content_handler/flutter_runner/platform_view.cc @@ -0,0 +1,550 @@ +// Copyright 2018 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#define RAPIDJSON_HAS_STDSTRING 1 + +#include "platform_view.h" + +#include + +#include "flutter/lib/ui/window/pointer_data.h" +#include "lib/app/cpp/connect.h" +#include "third_party/rapidjson/rapidjson/document.h" +#include "third_party/rapidjson/rapidjson/stringbuffer.h" +#include "third_party/rapidjson/rapidjson/writer.h" + +namespace flutter { + +constexpr char kFlutterPlatformChannel[] = "flutter/platform"; +constexpr char kTextInputChannel[] = "flutter/textinput"; +constexpr char kKeyEventChannel[] = "flutter/keyevent"; + +PlatformView::PlatformView( + PlatformView::Delegate& delegate, + std::string debug_label, + blink::TaskRunners task_runners, + app::ServiceProviderPtr parent_environment_service_provider, + mozart::ViewManagerPtr& view_manager, + f1dl::InterfaceRequest view_owner, + ui_mozart::MozartPtr mozart, + zx::eventpair export_token, + zx::eventpair import_token, + maxwell::ContextWriterPtr accessibility_context_writer, + OnMetricsUpdate on_session_metrics_did_change, + fxl::Closure session_error_callback) + : shell::PlatformView(delegate, std::move(task_runners)), + debug_label_(std::move(debug_label)), + view_listener_(this), + input_listener_(this), + ime_client_(this), + mozart_(std::move(mozart)), + accessibility_bridge_(std::move(accessibility_context_writer)), + surface_( + std::make_unique(mozart_, + debug_label_, + std::move(import_token), + std::move(on_session_metrics_did_change), + std::move(session_error_callback))) { + // Create the view. + view_manager->CreateView(view_.NewRequest(), // view + std::move(view_owner), // view owner + view_listener_.NewBinding(), // view listener + std::move(export_token), // export token + debug_label_ // diagnostic label + ); + + // Get the services from the created view. + app::ServiceProviderPtr service_provider; + view_->GetServiceProvider(service_provider.NewRequest()); + + // Get the input connection from the services of the view. + app::ConnectToService(service_provider.get(), input_connection_.NewRequest()); + + // Set the input listener on the input connection. + input_connection_->SetEventListener(input_listener_.NewBinding()); + + // Access the clipboard. + app::ConnectToService(parent_environment_service_provider.get(), + clipboard_.NewRequest()); + + // Finally! Register the native platform message handlers. + RegisterPlatformMessageHandlers(); +} + +PlatformView::~PlatformView() = default; + +void PlatformView::RegisterPlatformMessageHandlers() { + platform_message_handlers_[kFlutterPlatformChannel] = + std::bind(&PlatformView::HandleFlutterPlatformChannelPlatformMessage, // + this, // + std::placeholders::_1); + platform_message_handlers_[kTextInputChannel] = + std::bind(&PlatformView::HandleFlutterTextInputChannelPlatformMessage, // + this, // + std::placeholders::_1); +} + +mozart::ViewPtr& PlatformView::GetMozartView() { + return view_; +} + +// |mozart::ViewListener| +void PlatformView::OnPropertiesChanged( + mozart::ViewPropertiesPtr properties, + const OnPropertiesChangedCallback& callback) { + UpdateViewportMetrics(properties->view_layout); + callback(); +} + +void PlatformView::UpdateViewportMetrics(const mozart::ViewLayoutPtr& layout) { + if (!layout) { + return; + } + + metrics_.size.width = layout->size->width; + metrics_.size.height = layout->size->height; + + metrics_.padding.left = layout->inset->left; + metrics_.padding.top = layout->inset->top; + metrics_.padding.right = layout->inset->right; + metrics_.padding.bottom = layout->inset->bottom; + + FlushViewportMetrics(); +} + +void PlatformView::UpdateViewportMetrics(double pixel_ratio) { + metrics_.scale = pixel_ratio; + + FlushViewportMetrics(); +} + +void PlatformView::FlushViewportMetrics() { + const auto scale = metrics_.scale; + blink::ViewportMetrics metrics = { + .device_pixel_ratio = static_cast(scale), + + .physical_width = static_cast(metrics_.size.width * scale), + .physical_height = static_cast(metrics_.size.height * scale), + + .physical_padding_top = + static_cast(metrics_.padding.top * scale), + .physical_padding_right = + static_cast(metrics_.padding.right * scale), + .physical_padding_bottom = + static_cast(metrics_.padding.bottom * scale), + .physical_padding_left = + static_cast(metrics_.padding.left * scale), + + .physical_view_inset_top = + static_cast(metrics_.view_inset.top * scale), + .physical_view_inset_right = + static_cast(metrics_.view_inset.right * scale), + .physical_view_inset_bottom = + static_cast(metrics_.view_inset.bottom * scale), + .physical_view_inset_left = + static_cast(metrics_.view_inset.left * scale), + }; + + SetViewportMetrics(metrics); +} + +// |mozart::InputMethodEditorClient| +void PlatformView::DidUpdateState(mozart::TextInputStatePtr state, + mozart::InputEventPtr event) { + rapidjson::Document document; + auto& allocator = document.GetAllocator(); + rapidjson::Value encoded_state(rapidjson::kObjectType); + encoded_state.AddMember("text", state->text.get(), allocator); + encoded_state.AddMember("selectionBase", state->selection->base, allocator); + encoded_state.AddMember("selectionExtent", state->selection->extent, + allocator); + switch (state->selection->affinity) { + case mozart::TextAffinity::UPSTREAM: + encoded_state.AddMember("selectionAffinity", + rapidjson::Value("TextAffinity.upstream"), + allocator); + break; + case mozart::TextAffinity::DOWNSTREAM: + encoded_state.AddMember("selectionAffinity", + rapidjson::Value("TextAffinity.downstream"), + allocator); + break; + } + encoded_state.AddMember("selectionIsDirectional", true, allocator); + encoded_state.AddMember("composingBase", state->composing->start, allocator); + encoded_state.AddMember("composingExtent", state->composing->end, allocator); + + rapidjson::Value args(rapidjson::kArrayType); + args.PushBack(current_text_input_client_, allocator); + args.PushBack(encoded_state, allocator); + + document.SetObject(); + document.AddMember("method", + rapidjson::Value("TextInputClient.updateEditingState"), + allocator); + document.AddMember("args", args, allocator); + + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + document.Accept(writer); + + const uint8_t* data = reinterpret_cast(buffer.GetString()); + DispatchPlatformMessage(fxl::MakeRefCounted( + kTextInputChannel, // channel + std::vector(data, data + buffer.GetSize()), // message + nullptr) // response + ); +} + +// |mozart::InputMethodEditorClient| +void PlatformView::OnAction(mozart::InputMethodAction action) { + rapidjson::Document document; + auto& allocator = document.GetAllocator(); + + rapidjson::Value args(rapidjson::kArrayType); + args.PushBack(current_text_input_client_, allocator); + + // Done is currently the only text input action defined by Flutter. + args.PushBack("TextInputAction.done", allocator); + + document.SetObject(); + document.AddMember( + "method", rapidjson::Value("TextInputClient.performAction"), allocator); + document.AddMember("args", args, allocator); + + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + document.Accept(writer); + + const uint8_t* data = reinterpret_cast(buffer.GetString()); + DispatchPlatformMessage(fxl::MakeRefCounted( + kTextInputChannel, // channel + std::vector(data, data + buffer.GetSize()), // message + nullptr) // response + ); +} + +// |mozart::InputListener| +void PlatformView::OnEvent(mozart::InputEventPtr event, + const OnEventCallback& callback) { + using Type = mozart::InputEvent::Tag; + switch (event->which()) { + case Type::POINTER: + callback(OnHandlePointerEvent(event->get_pointer())); + return; + case Type::KEYBOARD: + callback(OnHandleKeyboardEvent(event->get_keyboard())); + return; + case Type::FOCUS: + callback(OnHandleFocusEvent(event->get_focus())); + return; + case Type::__UNKNOWN__: + break; + } + + callback(false); +} + +static blink::PointerData::Change GetChangeFromPointerEventPhase( + mozart::PointerEvent::Phase phase) { + switch (phase) { + case mozart::PointerEvent::Phase::ADD: + return blink::PointerData::Change::kAdd; + case mozart::PointerEvent::Phase::HOVER: + return blink::PointerData::Change::kHover; + case mozart::PointerEvent::Phase::DOWN: + return blink::PointerData::Change::kDown; + case mozart::PointerEvent::Phase::MOVE: + return blink::PointerData::Change::kMove; + case mozart::PointerEvent::Phase::UP: + return blink::PointerData::Change::kUp; + case mozart::PointerEvent::Phase::REMOVE: + return blink::PointerData::Change::kRemove; + case mozart::PointerEvent::Phase::CANCEL: + return blink::PointerData::Change::kCancel; + default: + return blink::PointerData::Change::kCancel; + } +} + +static blink::PointerData::DeviceKind GetKindFromPointerType( + mozart::PointerEvent::Type type) { + switch (type) { + case mozart::PointerEvent::Type::TOUCH: + return blink::PointerData::DeviceKind::kTouch; + case mozart::PointerEvent::Type::MOUSE: + return blink::PointerData::DeviceKind::kMouse; + default: + return blink::PointerData::DeviceKind::kTouch; + } +} + +bool PlatformView::OnHandlePointerEvent( + const mozart::PointerEventPtr& pointer) { + blink::PointerData pointer_data; + pointer_data.time_stamp = pointer->event_time / 1000; + pointer_data.change = GetChangeFromPointerEventPhase(pointer->phase); + pointer_data.kind = GetKindFromPointerType(pointer->type); + pointer_data.device = pointer->pointer_id; + pointer_data.physical_x = pointer->x * metrics_.scale; + pointer_data.physical_y = pointer->y * metrics_.scale; + + switch (pointer_data.change) { + case blink::PointerData::Change::kDown: + down_pointers_.insert(pointer_data.device); + break; + case blink::PointerData::Change::kCancel: + case blink::PointerData::Change::kUp: + down_pointers_.erase(pointer_data.device); + break; + case blink::PointerData::Change::kMove: + if (down_pointers_.count(pointer_data.device) == 0) { + pointer_data.change = blink::PointerData::Change::kHover; + } + break; + case blink::PointerData::Change::kAdd: + if (down_pointers_.count(pointer_data.device) != 0) { + FXL_DLOG(ERROR) << "Received add event for down pointer."; + } + break; + case blink::PointerData::Change::kRemove: + if (down_pointers_.count(pointer_data.device) != 0) { + FXL_DLOG(ERROR) << "Received remove event for down pointer."; + } + break; + case blink::PointerData::Change::kHover: + if (down_pointers_.count(pointer_data.device) != 0) { + FXL_DLOG(ERROR) << "Received hover event for down pointer."; + } + break; + } + + auto packet = std::make_unique(1); + packet->SetPointerData(0, pointer_data); + DispatchPointerDataPacket(std::move(packet)); + return true; +} + +bool PlatformView::OnHandleKeyboardEvent( + const mozart::KeyboardEventPtr& keyboard) { + const char* type = nullptr; + if (keyboard->phase == mozart::KeyboardEvent::Phase::PRESSED) { + type = "keydown"; + } else if (keyboard->phase == mozart::KeyboardEvent::Phase::REPEAT) { + type = "keydown"; // TODO change this to keyrepeat + } else if (keyboard->phase == mozart::KeyboardEvent::Phase::RELEASED) { + type = "keyup"; + } + + if (type == nullptr) { + FXL_DLOG(ERROR) << "Unknown key event phase."; + return false; + } + + rapidjson::Document document; + auto& allocator = document.GetAllocator(); + document.SetObject(); + document.AddMember("type", rapidjson::Value(type, strlen(type)), allocator); + document.AddMember("keymap", rapidjson::Value("fuchsia"), allocator); + document.AddMember("hidUsage", keyboard->hid_usage, allocator); + document.AddMember("codePoint", keyboard->code_point, allocator); + document.AddMember("modifiers", keyboard->modifiers, allocator); + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + document.Accept(writer); + + const uint8_t* data = reinterpret_cast(buffer.GetString()); + DispatchPlatformMessage(fxl::MakeRefCounted( + kKeyEventChannel, // channel + std::vector(data, data + buffer.GetSize()), // data + nullptr) // response + ); + + return true; +} + +bool PlatformView::OnHandleFocusEvent(const mozart::FocusEventPtr& focus) { + if (!focus->focused && current_text_input_client_ != 0) { + current_text_input_client_ = 0; + if (ime_) { + ime_->Hide(); + ime_ = nullptr; + } + if (ime_client_.is_bound()) { + ime_client_.Unbind(); + } + return true; + } + return false; +} + +// |shell::PlatformView| +std::unique_ptr PlatformView::CreateRenderingSurface() { + // This platform does not repeatly lose and gain a surface connection. So the + // surface is setup once during platform view setup and and returned to the + // shell on the initial (and only) |NotifyCreated| call. + return std::move(surface_); +} + +// |shell::PlatformView| +void PlatformView::HandlePlatformMessage( + fxl::RefPtr message) { + if (!message) { + return; + } + auto found = platform_message_handlers_.find(message->channel()); + if (found == platform_message_handlers_.end()) { + FXL_DLOG(ERROR) + << "Platform view received message on channel '" << message->channel() + << "' with no registed handler. And empty response will be generated. " + "Please implement the native message handler."; + PlatformView::HandlePlatformMessage(std::move(message)); + return; + } + found->second(std::move(message)); +} + +// |shell::PlatformView| +void PlatformView::UpdateSemantics(blink::SemanticsNodeUpdates update) { + accessibility_bridge_.UpdateSemantics(update); +} + +// Channel handler for kFlutterPlatformChannel +void PlatformView::HandleFlutterPlatformChannelPlatformMessage( + fxl::RefPtr message) { + FXL_DCHECK(message->channel() == kFlutterPlatformChannel); + const auto& data = message->data(); + rapidjson::Document document; + document.Parse(reinterpret_cast(data.data()), data.size()); + if (document.HasParseError() || !document.IsObject()) { + return; + } + + auto root = document.GetObject(); + auto method = root.FindMember("method"); + if (method == root.MemberEnd() || !method->value.IsString()) { + return; + } + + fxl::RefPtr response = message->response(); + if (method->value == "Clipboard.setData") { + auto text = root["args"]["text"].GetString(); + clipboard_->Push(text); + response->CompleteEmpty(); + } else if (method->value == "Clipboard.getData") { + clipboard_->Peek([response](const f1dl::String& text) { + rapidjson::StringBuffer json_buffer; + rapidjson::Writer writer(json_buffer); + writer.StartArray(); + writer.StartObject(); + writer.Key("text"); + writer.String(text); + writer.EndObject(); + writer.EndArray(); + std::string result = json_buffer.GetString(); + response->Complete(std::vector{result.begin(), result.end()}); + }); + } else { + response->CompleteEmpty(); + } +} + +// Channel handler for kTextInputChannel +void PlatformView::HandleFlutterTextInputChannelPlatformMessage( + fxl::RefPtr message) { + FXL_DCHECK(message->channel() == kTextInputChannel); + const auto& data = message->data(); + rapidjson::Document document; + document.Parse(reinterpret_cast(data.data()), data.size()); + if (document.HasParseError() || !document.IsObject()) { + return; + } + auto root = document.GetObject(); + auto method = root.FindMember("method"); + if (method == root.MemberEnd() || !method->value.IsString()) { + return; + } + + if (method->value == "TextInput.show") { + if (ime_) { + ime_->Show(); + } + } else if (method->value == "TextInput.hide") { + if (ime_) { + ime_->Hide(); + } + } else if (method->value == "TextInput.setClient") { + current_text_input_client_ = 0; + if (ime_client_.is_bound()) + ime_client_.Unbind(); + ime_ = nullptr; + + auto args = root.FindMember("args"); + if (args == root.MemberEnd() || !args->value.IsArray() || + args->value.Size() != 2) + return; + const auto& configuration = args->value[1]; + if (!configuration.IsObject()) { + return; + } + // TODO(abarth): Read the keyboard type from the configuration. + current_text_input_client_ = args->value[0].GetInt(); + mozart::TextInputStatePtr state = mozart::TextInputState::New(); + state->text = std::string(); + state->selection = mozart::TextSelection::New(); + state->composing = mozart::TextRange::New(); + input_connection_->GetInputMethodEditor( + mozart::KeyboardType::TEXT, mozart::InputMethodAction::DONE, + std::move(state), ime_client_.NewBinding(), ime_.NewRequest()); + } else if (method->value == "TextInput.setEditingState") { + if (ime_) { + auto args_it = root.FindMember("args"); + if (args_it == root.MemberEnd() || !args_it->value.IsObject()) { + return; + } + const auto& args = args_it->value; + mozart::TextInputStatePtr state = mozart::TextInputState::New(); + state->selection = mozart::TextSelection::New(); + state->composing = mozart::TextRange::New(); + // TODO(abarth): Deserialize state. + auto text = args.FindMember("text"); + if (text != args.MemberEnd() && text->value.IsString()) + state->text = text->value.GetString(); + auto selection_base = args.FindMember("selectionBase"); + if (selection_base != args.MemberEnd() && selection_base->value.IsInt()) + state->selection->base = selection_base->value.GetInt(); + auto selection_extent = args.FindMember("selectionExtent"); + if (selection_extent != args.MemberEnd() && + selection_extent->value.IsInt()) + state->selection->extent = selection_extent->value.GetInt(); + auto selection_affinity = args.FindMember("selectionAffinity"); + if (selection_affinity != args.MemberEnd() && + selection_affinity->value.IsString() && + selection_affinity->value == "TextAffinity.upstream") + state->selection->affinity = mozart::TextAffinity::UPSTREAM; + else + state->selection->affinity = mozart::TextAffinity::DOWNSTREAM; + // We ignore selectionIsDirectional because that concept doesn't exist on + // Fuchsia. + auto composing_base = args.FindMember("composingBase"); + if (composing_base != args.MemberEnd() && composing_base->value.IsInt()) + state->composing->start = composing_base->value.GetInt(); + auto composing_extent = args.FindMember("composingExtent"); + if (composing_extent != args.MemberEnd() && + composing_extent->value.IsInt()) + state->composing->end = composing_extent->value.GetInt(); + ime_->SetState(std::move(state)); + } + } else if (method->value == "TextInput.clearClient") { + current_text_input_client_ = 0; + if (ime_client_.is_bound()) + ime_client_.Unbind(); + ime_ = nullptr; + } else { + FXL_DLOG(ERROR) << "Unknown " << message->channel() << " method " + << method->value.GetString(); + } +} + +} // namespace flutter diff --git a/content_handler/flutter_runner/platform_view.h b/content_handler/flutter_runner/platform_view.h new file mode 100644 index 0000000000000..dcb64e1b7bf92 --- /dev/null +++ b/content_handler/flutter_runner/platform_view.h @@ -0,0 +1,119 @@ +// Copyright 2018 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#pragma once + +#include +#include + +#include "accessibility_bridge.h" +#include "flutter/lib/ui/window/viewport_metrics.h" +#include "flutter/shell/common/platform_view.h" +#include "lib/clipboard/fidl/clipboard.fidl.h" +#include "lib/fidl/cpp/bindings/binding.h" +#include "lib/fxl/macros.h" +#include "lib/ui/input/fidl/input_connection.fidl.h" +#include "lib/ui/views/fidl/view_manager.fidl.h" +#include "lib/ui/views/fidl/views.fidl.h" +#include "surface.h" + +namespace flutter { + +// The per engine component residing on the platform thread is responsible for +// all platform specific integrations. +class PlatformView final : public shell::PlatformView, + public mozart::ViewListener, + public mozart::InputMethodEditorClient, + public mozart::InputListener { + public: + PlatformView(PlatformView::Delegate& delegate, + std::string debug_label, + blink::TaskRunners task_runners, + app::ServiceProviderPtr parent_environment_service_provider, + mozart::ViewManagerPtr& view_manager, + f1dl::InterfaceRequest view_owner, + ui_mozart::MozartPtr mozart, + zx::eventpair export_token, + zx::eventpair import_token, + maxwell::ContextWriterPtr accessibility_context_writer, + OnMetricsUpdate on_session_metrics_did_change, + fxl::Closure session_error_callback); + + ~PlatformView(); + + void UpdateViewportMetrics(double pixel_ratio); + + mozart::ViewPtr& GetMozartView(); + + private: + const std::string debug_label_; + mozart::ViewPtr view_; + f1dl::Binding view_listener_; + mozart::InputConnectionPtr input_connection_; + f1dl::Binding input_listener_; + int current_text_input_client_ = 0; + f1dl::Binding ime_client_; + mozart::InputMethodEditorPtr ime_; + modular::ClipboardPtr clipboard_; + ui_mozart::MozartPtr mozart_; + AccessibilityBridge accessibility_bridge_; + std::unique_ptr surface_; + blink::LogicalMetrics metrics_; + std::set down_pointers_; + std::map< + std::string /* channel */, + std::function /* message */)> /* handler */> + platform_message_handlers_; + + void RegisterPlatformMessageHandlers(); + + void UpdateViewportMetrics(const mozart::ViewLayoutPtr& layout); + + void FlushViewportMetrics(); + + // |mozart::ViewListener| + void OnPropertiesChanged( + mozart::ViewPropertiesPtr properties, + const OnPropertiesChangedCallback& callback) override; + + // |mozart::InputMethodEditorClient| + void DidUpdateState(mozart::TextInputStatePtr state, + mozart::InputEventPtr event) override; + + // |mozart::InputMethodEditorClient| + void OnAction(mozart::InputMethodAction action) override; + + // |mozart::InputListener| + void OnEvent(mozart::InputEventPtr event, + const OnEventCallback& callback) override; + + bool OnHandlePointerEvent(const mozart::PointerEventPtr& pointer); + + bool OnHandleKeyboardEvent(const mozart::KeyboardEventPtr& keyboard); + + bool OnHandleFocusEvent(const mozart::FocusEventPtr& focus); + + // |shell::PlatformView| + std::unique_ptr CreateRenderingSurface() override; + + // |shell::PlatformView| + void HandlePlatformMessage( + fxl::RefPtr message) override; + + // |shell::PlatformView| + void UpdateSemantics(blink::SemanticsNodeUpdates update) override; + + // Channel handler for kFlutterPlatformChannel + void HandleFlutterPlatformChannelPlatformMessage( + fxl::RefPtr message); + + // Channel handler for kTextInputChannel + void HandleFlutterTextInputChannelPlatformMessage( + fxl::RefPtr message); + + FXL_DISALLOW_COPY_AND_ASSIGN(PlatformView); +}; + +} // namespace flutter diff --git a/content_handler/flutter_runner/session_connection.cc b/content_handler/flutter_runner/session_connection.cc new file mode 100644 index 0000000000000..5b50ee678a8dd --- /dev/null +++ b/content_handler/flutter_runner/session_connection.cc @@ -0,0 +1,83 @@ +// Copyright 2017 The Chromium 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 "session_connection.h" + +#include "lib/ui/scenic/fidl_helpers.h" + +namespace flutter { + +SessionConnection::SessionConnection( + const ui_mozart::MozartPtr& mozart, + std::string debug_label, + zx::eventpair import_token, + OnMetricsUpdate session_metrics_did_change_callback, + fxl::Closure session_error_callback) + : debug_label_(std::move(debug_label)), + session_(mozart.get()), + root_node_(&session_), + surface_producer_(std::make_unique(&session_)), + scene_update_context_(&session_, surface_producer_.get()), + metrics_changed_callback_( + std::move(session_metrics_did_change_callback)) { + session_.set_error_handler(std::move(session_error_callback)); + session_.set_event_handler(std::bind(&SessionConnection::OnSessionEvents, + this, std::placeholders::_1)); + + root_node_.Bind(std::move(import_token)); + root_node_.SetEventMask(scenic::kMetricsEventMask); + session_.Present(0, [](ui_mozart::PresentationInfoPtr info) {}); +} + +SessionConnection::~SessionConnection() = default; + +void SessionConnection::OnSessionEvents( + f1dl::Array events) { + using Type = scenic::Event::Tag; + + for (auto& event : events) { + switch (event->get_scenic()->which()) { + case Type::METRICS: { + if (event->get_scenic()->get_metrics()->node_id == root_node_.id()) { + auto& metrics = event->get_scenic()->get_metrics()->metrics; + double device_pixel_ratio = metrics->scale_x; + scene_update_context_.set_metrics(std::move(metrics)); + if (metrics_changed_callback_) { + metrics_changed_callback_(device_pixel_ratio); + } + } + } break; + default: + break; + } + } +} + +void SessionConnection::Present(flow::CompositorContext::ScopedFrame& frame) { + // Flush all session ops. Paint tasks have not yet executed but those are + // fenced. The compositor can start processing ops while we finalize paint + // tasks. + session_.Present(0, // presentation_time. (placeholder). + [](ui_mozart::PresentationInfoPtr) {} // callback + ); + + // Execute paint tasks and signal fences. + auto surfaces_to_submit = scene_update_context_.ExecutePaintTasks(frame); + + // Tell the surface producer that a present has occurred so it can perform + // book-keeping on buffer caches. + surface_producer_->OnSurfacesPresented(std::move(surfaces_to_submit)); + + // Prepare for the next frame. These ops won't be processed till the next + // present. + EnqueueClearOps(); +} + +void SessionConnection::EnqueueClearOps() { + // We are going to be sending down a fresh node hierarchy every frame. So just + // enqueue a detach op on the imported root node. + session_.Enqueue(scenic_lib::NewDetachChildrenOp(root_node_.id())); +} + +} // namespace flutter diff --git a/content_handler/flutter_runner/session_connection.h b/content_handler/flutter_runner/session_connection.h new file mode 100644 index 0000000000000..8cc1375f55bd6 --- /dev/null +++ b/content_handler/flutter_runner/session_connection.h @@ -0,0 +1,62 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#pragma once + +#include "flutter/flow/compositor_context.h" +#include "flutter/flow/scene_update_context.h" +#include "lib/fidl/cpp/bindings/interface_handle.h" +#include "lib/fxl/functional/closure.h" +#include "lib/fxl/macros.h" +#include "lib/ui/scenic/client/resources.h" +#include "lib/ui/scenic/client/session.h" +#include "vulkan_surface_producer.h" +#include "zx/eventpair.h" + +namespace flutter { + +using OnMetricsUpdate = std::function; + +// The component residing on the GPU thread that is responsible for +// maintaining the Scenic session connection and presenting node updates. +class SessionConnection final { + public: + SessionConnection(const ui_mozart::MozartPtr& mozart, + std::string debug_label, + zx::eventpair import_token, + OnMetricsUpdate session_metrics_did_change_callback, + fxl::Closure session_error_callback); + + ~SessionConnection(); + + bool has_metrics() const { return scene_update_context_.has_metrics(); } + + const scenic::MetricsPtr& metrics() const { + return scene_update_context_.metrics(); + } + + flow::SceneUpdateContext& scene_update_context() { + return scene_update_context_; + } + + scenic_lib::ImportNode& root_node() { return root_node_; } + + void Present(flow::CompositorContext::ScopedFrame& frame); + + private: + const std::string debug_label_; + scenic_lib::Session session_; + scenic_lib::ImportNode root_node_; + std::unique_ptr surface_producer_; + flow::SceneUpdateContext scene_update_context_; + OnMetricsUpdate metrics_changed_callback_; + + void OnSessionEvents(f1dl::Array events); + + void EnqueueClearOps(); + + FXL_DISALLOW_COPY_AND_ASSIGN(SessionConnection); +}; + +} // namespace flutter diff --git a/content_handler/flutter_runner/surface.cc b/content_handler/flutter_runner/surface.cc new file mode 100644 index 0000000000000..0b33118d86795 --- /dev/null +++ b/content_handler/flutter_runner/surface.cc @@ -0,0 +1,73 @@ +// Copyright 2018 The Fuchsia 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 "surface.h" + +#include +#include +#include +#include + +#include "lib/fxl/files/unique_fd.h" + +namespace flutter { + +Surface::Surface(const ui_mozart::MozartPtr& mozart, + std::string debug_label, + zx::eventpair import_token, + OnMetricsUpdate session_metrics_did_change_callback, + fxl::Closure session_error_callback) + : shell::Surface(std::make_unique( + mozart, + debug_label, + std::move(import_token), + std::move(session_metrics_did_change_callback), + std::move(session_error_callback))), + debug_label_(debug_label) {} + +Surface::~Surface() = default; + +// |shell::Surface| +bool Surface::IsValid() { + return valid_; +} + +// |shell::Surface| +std::unique_ptr Surface::AcquireFrame( + const SkISize& size) { + return std::make_unique( + nullptr, [](const shell::SurfaceFrame& surface_frame, SkCanvas* canvas) { + return true; + }); +} + +// |shell::Surface| +GrContext* Surface::GetContext() { + return nullptr; +} + +static zx_status_t DriverWatcher(int dirfd, + int event, + const char* fn, + void* cookie) { + if (event == WATCH_EVENT_ADD_FILE && !strcmp(fn, "000")) { + return ZX_ERR_STOP; + } + return ZX_OK; +} + +bool Surface::CanConnectToDisplay() { + constexpr char kDisplayDriverClass[] = "/dev/class/display"; + fxl::UniqueFD fd(open(kDisplayDriverClass, O_DIRECTORY | O_RDONLY)); + if (fd.get() < 0) { + FXL_DLOG(ERROR) << "Failed to open " << kDisplayDriverClass; + return false; + } + + zx_status_t status = fdio_watch_directory( + fd.get(), DriverWatcher, zx_deadline_after(ZX_SEC(1)), nullptr); + return status == ZX_ERR_STOP; +} + +} // namespace flutter diff --git a/content_handler/flutter_runner/surface.h b/content_handler/flutter_runner/surface.h new file mode 100644 index 0000000000000..2b4e61f0cbd11 --- /dev/null +++ b/content_handler/flutter_runner/surface.h @@ -0,0 +1,47 @@ +// Copyright 2018 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#pragma once + +#include "compositor_context.h" +#include "flutter/fml/memory/weak_ptr.h" +#include "flutter/shell/common/surface.h" +#include "lib/fxl/macros.h" + +namespace flutter { + +// The interface between the Flutter rasterizer and the underlying platform. May +// be constructed on any thread but will be used by the engine only on the GPU +// thread. +class Surface final : public shell::Surface { + public: + Surface(const ui_mozart::MozartPtr& mozart, + std::string debug_label, + zx::eventpair import_token, + OnMetricsUpdate session_metrics_did_change_callback, + fxl::Closure session_error_callback); + + ~Surface() override; + + private: + const bool valid_ = CanConnectToDisplay(); + const std::string debug_label_; + std::unique_ptr compositor_context_; + + // |shell::Surface| + bool IsValid() override; + + // |shell::Surface| + std::unique_ptr AcquireFrame( + const SkISize& size) override; + + // |shell::Surface| + GrContext* GetContext() override; + + static bool CanConnectToDisplay(); + + FXL_DISALLOW_COPY_AND_ASSIGN(Surface); +}; + +} // namespace flutter diff --git a/content_handler/flutter_runner/task_observers.cc b/content_handler/flutter_runner/task_observers.cc new file mode 100644 index 0000000000000..f23265e7b1653 --- /dev/null +++ b/content_handler/flutter_runner/task_observers.cc @@ -0,0 +1,43 @@ +// Copyright 2018 The Fuchsia 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 "task_observers.h" + +#include + +#include "lib/fsl/tasks/message_loop.h" + +namespace flutter { + +thread_local std::map tTaskObservers; + +static void ExecuteAfterTaskObservers() { + for (const auto& callback : tTaskObservers) { + callback.second(); + } +} + +void CurrentMessageLoopAddAfterTaskObserver(intptr_t key, + fxl::Closure observer) { + if (!observer) { + return; + } + + if (tTaskObservers.size() == 0) { + fsl::MessageLoop::GetCurrent()->SetAfterTaskCallback( + std::bind(&ExecuteAfterTaskObservers)); + } + + tTaskObservers[key] = observer; +} + +void CurrentMessageLoopRemoveAfterTaskObserver(intptr_t key) { + tTaskObservers.erase(key); + + if (tTaskObservers.size() == 0) { + fsl::MessageLoop::GetCurrent()->ClearAfterTaskCallback(); + } +} + +} // namespace flutter diff --git a/content_handler/flutter_runner/task_observers.h b/content_handler/flutter_runner/task_observers.h new file mode 100644 index 0000000000000..53f6b96222000 --- /dev/null +++ b/content_handler/flutter_runner/task_observers.h @@ -0,0 +1,16 @@ +// Copyright 2018 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#pragma once + +#include "lib/fxl/functional/closure.h" + +namespace flutter { + +void CurrentMessageLoopAddAfterTaskObserver(intptr_t key, + fxl::Closure observer); + +void CurrentMessageLoopRemoveAfterTaskObserver(intptr_t key); + +} // namespace flutter diff --git a/content_handler/flutter_runner/unique_fdio_ns.h b/content_handler/flutter_runner/unique_fdio_ns.h new file mode 100644 index 0000000000000..726137d729f85 --- /dev/null +++ b/content_handler/flutter_runner/unique_fdio_ns.h @@ -0,0 +1,34 @@ +// Copyright 2018 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#pragma once + +#include "fdio/namespace.h" +#include "lib/fxl/logging.h" +#include "lib/fxl/memory/unique_object.h" + +namespace flutter { + +struct UniqueFDIONSTraits { + static fdio_ns_t* InvalidValue() { return nullptr; } + + static bool IsValid(fdio_ns_t* ns) { return ns != InvalidValue(); } + + static void Free(fdio_ns_t* ns) { + auto status = fdio_ns_destroy(ns); + FXL_DCHECK(status == ZX_OK); + } +}; + +using UniqueFDIONS = fxl::UniqueObject; + +inline UniqueFDIONS UniqueFDIONSCreate() { + fdio_ns_t* ns = nullptr; + if (fdio_ns_create(&ns) == ZX_OK) { + return UniqueFDIONS{ns}; + } + return UniqueFDIONS{nullptr}; +} + +} // namespace flutter diff --git a/content_handler/flutter_runner/vulkan_surface.cc b/content_handler/flutter_runner/vulkan_surface.cc new file mode 100644 index 0000000000000..8a6ceb745a3e7 --- /dev/null +++ b/content_handler/flutter_runner/vulkan_surface.cc @@ -0,0 +1,413 @@ +// Copyright 2017 The Fuchsia 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 "vulkan_surface.h" + +#include + +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/gpu/GrBackendSemaphore.h" +#include "third_party/skia/include/gpu/GrBackendSurface.h" +#include "third_party/skia/include/gpu/GrContext.h" +#include "third_party/skia/src/gpu/vk/GrVkImage.h" + +namespace flutter { + +VulkanSurface::VulkanSurface(vulkan::VulkanProvider& vulkan_provider, + sk_sp context, + sk_sp backend_context, + scenic_lib::Session* session, + const SkISize& size) + : vulkan_provider_(vulkan_provider), + backend_context_(std::move(backend_context)), + session_(session), + wait_(this) { + FXL_DCHECK(session_); + + zx::vmo exported_vmo; + if (!AllocateDeviceMemory(std::move(context), size, exported_vmo)) { + FXL_DLOG(INFO) << "Could not allocate device memory."; + return; + } + + if (!CreateFences()) { + FXL_DLOG(INFO) << "Could not create signal fences."; + return; + } + + if (!PushSessionImageSetupOps(session, std::move(exported_vmo))) { + FXL_DLOG(INFO) << "Could not push session image setup ops."; + return; + } + + wait_.set_object(release_event_.get()); + wait_.set_trigger(ZX_EVENT_SIGNALED); + async_ = async_get_default(); + wait_.Begin(async_); + + // Probably not necessary as the events should be in the unsignalled state + // already. + Reset(); + + valid_ = true; +} + +VulkanSurface::~VulkanSurface() { + if (async_) { + wait_.Cancel(async_); + wait_.set_object(ZX_HANDLE_INVALID); + async_ = nullptr; + } +} + +bool VulkanSurface::IsValid() const { + return valid_; +} + +SkISize VulkanSurface::GetSize() const { + if (!valid_) { + return SkISize::Make(0, 0); + } + + return SkISize::Make(sk_surface_->width(), sk_surface_->height()); +} + +vulkan::VulkanHandle VulkanSurface::SemaphoreFromEvent( + const zx::event& event) const { + VkResult result; + VkSemaphore semaphore; + + zx::event semaphore_event; + zx_status_t status = event.duplicate(ZX_RIGHT_SAME_RIGHTS, &semaphore_event); + if (status != ZX_OK) { + FXL_DLOG(ERROR) << "failed to duplicate semaphore event"; + return vulkan::VulkanHandle(); + } + + VkSemaphoreCreateInfo create_info = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + }; + + result = VK_CALL_LOG_ERROR(vulkan_provider_.vk().CreateSemaphore( + vulkan_provider_.vk_device(), &create_info, nullptr, &semaphore)); + if (result != VK_SUCCESS) { + return vulkan::VulkanHandle(); + } + + VkImportSemaphoreFuchsiaHandleInfoKHR import_info = { + .sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FUCHSIA_HANDLE_INFO_KHR, + .pNext = nullptr, + .semaphore = semaphore, + .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_FUCHSIA_FENCE_BIT_KHR, + .handle = static_cast(semaphore_event.release())}; + + result = + VK_CALL_LOG_ERROR(vulkan_provider_.vk().ImportSemaphoreFuchsiaHandleKHR( + vulkan_provider_.vk_device(), &import_info)); + if (result != VK_SUCCESS) { + return vulkan::VulkanHandle(); + } + + return vulkan::VulkanHandle( + semaphore, [&vulkan_provider = vulkan_provider_](VkSemaphore semaphore) { + vulkan_provider.vk().DestroySemaphore(vulkan_provider.vk_device(), + semaphore, nullptr); + }); +} + +bool VulkanSurface::CreateFences() { + if (zx::event::create(0, &acquire_event_) != ZX_OK) { + return false; + } + + acquire_semaphore_ = SemaphoreFromEvent(acquire_event_); + if (!acquire_semaphore_) { + FXL_DLOG(ERROR) << "failed to create acquire semaphore"; + return false; + } + + if (zx::event::create(0, &release_event_) != ZX_OK) { + return false; + } + + command_buffer_fence_ = vulkan_provider_.CreateFence(); + + return true; +} + +bool VulkanSurface::AllocateDeviceMemory(sk_sp context, + const SkISize& size, + zx::vmo& exported_vmo) { + if (size.isEmpty()) { + return false; + } + + if (backend_context_ == nullptr) { + return false; + } + + // Create the image. + const VkImageCreateInfo image_create_info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = nullptr, + .flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT, + .imageType = VK_IMAGE_TYPE_2D, + .format = VK_FORMAT_B8G8R8A8_UNORM, + .extent = VkExtent3D{static_cast(size.width()), + static_cast(size.height()), 1}, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + }; + + { + VkImage vk_image = VK_NULL_HANDLE; + + if (VK_CALL_LOG_ERROR(vulkan_provider_.vk().CreateImage( + vulkan_provider_.vk_device(), &image_create_info, nullptr, + &vk_image)) != VK_SUCCESS) { + return false; + } + + vk_image_ = {vk_image, + [& vulkan_provider = vulkan_provider_](VkImage image) { + vulkan_provider.vk().DestroyImage( + vulkan_provider.vk_device(), image, NULL); + }}; + } + + // Create the memory. + VkMemoryRequirements memory_reqs; + vulkan_provider_.vk().GetImageMemoryRequirements(vulkan_provider_.vk_device(), + vk_image_, &memory_reqs); + + uint32_t memory_type = 0; + for (; memory_type < 32; memory_type++) { + if ((memory_reqs.memoryTypeBits & (1 << memory_type))) { + break; + } + } + VkExportMemoryAllocateInfoKHR export_allocate_info = { + .sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO_KHR, + .pNext = nullptr, + .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_FUCHSIA_VMO_BIT_KHR}; + const VkMemoryAllocateInfo alloc_info = { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .pNext = &export_allocate_info, + .allocationSize = memory_reqs.size, + .memoryTypeIndex = memory_type, + }; + + { + VkDeviceMemory vk_memory = VK_NULL_HANDLE; + if (VK_CALL_LOG_ERROR(vulkan_provider_.vk().AllocateMemory( + vulkan_provider_.vk_device(), &alloc_info, NULL, &vk_memory)) != + VK_SUCCESS) { + return false; + } + + vk_memory_ = {vk_memory, [& vulkan_provider = + vulkan_provider_](VkDeviceMemory memory) { + vulkan_provider.vk().FreeMemory(vulkan_provider.vk_device(), + memory, NULL); + }}; + } + + // Bind image memory. + if (VK_CALL_LOG_ERROR(vulkan_provider_.vk().BindImageMemory( + vulkan_provider_.vk_device(), vk_image_, vk_memory_, 0)) != + VK_SUCCESS) { + return false; + } + + { + // Acquire the VMO for the device memory. + uint32_t vmo_handle = 0; + + VkMemoryGetFuchsiaHandleInfoKHR get_handle_info = { + VK_STRUCTURE_TYPE_MEMORY_GET_FUCHSIA_HANDLE_INFO_KHR, nullptr, + vk_memory_, VK_EXTERNAL_MEMORY_HANDLE_TYPE_FUCHSIA_VMO_BIT_KHR}; + if (VK_CALL_LOG_ERROR(vulkan_provider_.vk().GetMemoryFuchsiaHandleKHR( + vulkan_provider_.vk_device(), &get_handle_info, &vmo_handle)) != + VK_SUCCESS) { + return false; + } + + exported_vmo.reset(static_cast(vmo_handle)); + } + + // Assert that the VMO size was sufficient. + size_t vmo_size = 0; + if (exported_vmo.get_size(&vmo_size) != ZX_OK || + vmo_size < memory_reqs.size) { + return false; + } + + return SetupSkiaSurface(std::move(context), size, image_create_info, + memory_reqs); +} + +bool VulkanSurface::SetupSkiaSurface(sk_sp context, + const SkISize& size, + const VkImageCreateInfo& image_create_info, + const VkMemoryRequirements& memory_reqs) { + if (context == nullptr) { + return false; + } + + const GrVkImageInfo image_info = { + .fImage = vk_image_, + .fAlloc = {vk_memory_, 0, memory_reqs.size, 0}, + .fImageTiling = image_create_info.tiling, + .fImageLayout = image_create_info.initialLayout, + .fFormat = image_create_info.format, + .fLevelCount = image_create_info.mipLevels, + }; + + GrBackendRenderTarget sk_render_target(size.width(), size.height(), 0, 0, + image_info); + + SkSurfaceProps sk_surface_props( + SkSurfaceProps::InitType::kLegacyFontHost_InitType); + + auto sk_surface = + SkSurface::MakeFromBackendRenderTarget(context.get(), // + sk_render_target, // + kTopLeft_GrSurfaceOrigin, // + nullptr, // + &sk_surface_props // + ); + + if (!sk_surface || sk_surface->getCanvas() == nullptr) { + return false; + } + sk_surface_ = std::move(sk_surface); + + return true; +} + +bool VulkanSurface::PushSessionImageSetupOps(scenic_lib::Session* session, + zx::vmo exported_vmo) { + if (sk_surface_ == nullptr) { + return false; + } + + scenic_lib::Memory memory(session, std::move(exported_vmo), + ui::gfx::MemoryType::VK_DEVICE_MEMORY); + + auto image_info = ui::gfx::ImageInfo::New(); + image_info->width = sk_surface_->width(); + image_info->height = sk_surface_->height(); + image_info->stride = 4 * sk_surface_->width(); + image_info->pixel_format = ui::gfx::ImageInfo::PixelFormat::BGRA_8; + image_info->color_space = ui::gfx::ImageInfo::ColorSpace::SRGB; + image_info->tiling = ui::gfx::ImageInfo::Tiling::LINEAR; + + session_image_ = std::make_unique( + memory, 0 /* memory offset */, std::move(image_info)); + + return session_image_ != nullptr; +} + +scenic_lib::Image* VulkanSurface::GetImage() { + if (!valid_) { + return 0; + } + return session_image_.get(); +} + +sk_sp VulkanSurface::GetSkiaSurface() const { + return valid_ ? sk_surface_ : nullptr; +} + +size_t VulkanSurface::AdvanceAndGetAge() { + age_++; + return age_; +} + +bool VulkanSurface::FlushSessionAcquireAndReleaseEvents() { + zx::event acquire, release; + + if (acquire_event_.duplicate(ZX_RIGHT_SAME_RIGHTS, &acquire) != ZX_OK || + release_event_.duplicate(ZX_RIGHT_SAME_RIGHTS, &release) != ZX_OK) { + return false; + } + + session_->EnqueueAcquireFence(std::move(acquire)); + session_->EnqueueReleaseFence(std::move(release)); + age_ = 0; + return true; +} + +void VulkanSurface::SignalWritesFinished( + std::function on_writes_committed) { + FXL_DCHECK(on_writes_committed); + + if (!valid_) { + on_writes_committed(); + return; + } + + FXL_CHECK(pending_on_writes_committed_ == nullptr) + << "Attempted to signal a write on the surface when the previous write " + "has not yet been acknowledged by the compositor."; + + pending_on_writes_committed_ = on_writes_committed; +} + +void VulkanSurface::Reset() { + if (acquire_event_.signal(ZX_EVENT_SIGNALED, 0u) != ZX_OK || + release_event_.signal(ZX_EVENT_SIGNALED, 0u) != ZX_OK) { + valid_ = false; + FXL_DLOG(ERROR) + << "Could not reset fences. The surface is no longer valid."; + } + + VkFence fence = command_buffer_fence_; + + if (command_buffer_) { + VK_CALL_LOG_ERROR(vulkan_provider_.vk().WaitForFences( + vulkan_provider_.vk_device(), 1, &fence, VK_TRUE, UINT64_MAX)); + command_buffer_.reset(); + } + + VK_CALL_LOG_ERROR(vulkan_provider_.vk().ResetFences( + vulkan_provider_.vk_device(), 1, &fence)); + + // Need to make a new acquire semaphore every frame or else validation layers + // get confused about why no one is waiting on it in this VkInstance + acquire_semaphore_.Reset(); + acquire_semaphore_ = SemaphoreFromEvent(acquire_event_); + if (!acquire_semaphore_) { + FXL_DLOG(ERROR) << "failed to create acquire semaphore"; + } + + // It is safe for the caller to collect the surface in the callback. + auto callback = pending_on_writes_committed_; + pending_on_writes_committed_ = nullptr; + if (callback) { + callback(); + } +} + +async_wait_result_t VulkanSurface::OnHandleReady( + async_t* async, + zx_status_t status, + const zx_packet_signal_t* signal) { + if (status != ZX_OK) + return ASYNC_WAIT_FINISHED; + FXL_DCHECK(signal->observed & ZX_EVENT_SIGNALED); + Reset(); + return ASYNC_WAIT_AGAIN; +} + +} // namespace flutter diff --git a/content_handler/flutter_runner/vulkan_surface.h b/content_handler/flutter_runner/vulkan_surface.h new file mode 100644 index 0000000000000..6897f21fbb320 --- /dev/null +++ b/content_handler/flutter_runner/vulkan_surface.h @@ -0,0 +1,122 @@ +// Copyright 2017 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#pragma once + +#include +#include +#include + +#include + +#include "flutter/flow/scene_update_context.h" +#include "flutter/vulkan/vulkan_command_buffer.h" +#include "flutter/vulkan/vulkan_handle.h" +#include "flutter/vulkan/vulkan_proc_table.h" +#include "flutter/vulkan/vulkan_provider.h" +#include "lib/fxl/macros.h" +#include "lib/ui/scenic/client/resources.h" +#include "third_party/skia/include/core/SkSurface.h" +#include "third_party/skia/include/gpu/vk/GrVkBackendContext.h" + +namespace flutter { + +class VulkanSurface final + : public flow::SceneUpdateContext::SurfaceProducerSurface { + public: + VulkanSurface(vulkan::VulkanProvider& vulkan_provider, + sk_sp context, + sk_sp backend_context, + scenic_lib::Session* session, + const SkISize& size); + + ~VulkanSurface() override; + + // |flow::SceneUpdateContext::SurfaceProducerSurface| + size_t AdvanceAndGetAge() override; + + // |flow::SceneUpdateContext::SurfaceProducerSurface| + bool FlushSessionAcquireAndReleaseEvents() override; + + // |flow::SceneUpdateContext::SurfaceProducerSurface| + bool IsValid() const override; + + // |flow::SceneUpdateContext::SurfaceProducerSurface| + SkISize GetSize() const override; + + // Note: It is safe for the caller to collect the surface in the + // |on_writes_committed| callback. + void SignalWritesFinished( + std::function on_writes_committed) override; + + // |flow::SceneUpdateContext::SurfaceProducerSurface| + scenic_lib::Image* GetImage() override; + + // |flow::SceneUpdateContext::SurfaceProducerSurface| + sk_sp GetSkiaSurface() const override; + + const vulkan::VulkanHandle& GetVkImage() { return vk_image_; } + + const vulkan::VulkanHandle& GetAcquireVkSemaphore() { + return acquire_semaphore_; + } + + vulkan::VulkanCommandBuffer* GetCommandBuffer( + const vulkan::VulkanHandle& pool) { + if (!command_buffer_) + command_buffer_ = std::make_unique( + vulkan_provider_.vk(), vulkan_provider_.vk_device(), pool); + return command_buffer_.get(); + } + + const vulkan::VulkanHandle& GetCommandBufferFence() { + return command_buffer_fence_; + } + + private: + async_wait_result_t OnHandleReady(async_t* async, + zx_status_t status, + const zx_packet_signal_t* signal); + + bool AllocateDeviceMemory(sk_sp context, + const SkISize& size, + zx::vmo& exported_vmo); + + bool SetupSkiaSurface(sk_sp context, + const SkISize& size, + const VkImageCreateInfo& image_create_info, + const VkMemoryRequirements& memory_reqs); + + bool CreateFences(); + + bool PushSessionImageSetupOps(scenic_lib::Session* session, + zx::vmo exported_vmo); + + void Reset(); + + vulkan::VulkanHandle SemaphoreFromEvent( + const zx::event& event) const; + + vulkan::VulkanProvider& vulkan_provider_; + sk_sp backend_context_; + scenic_lib::Session* session_; + vulkan::VulkanHandle vk_image_; + vulkan::VulkanHandle vk_memory_; + vulkan::VulkanHandle command_buffer_fence_; + sk_sp sk_surface_; + std::unique_ptr session_image_; + zx::event acquire_event_; + vulkan::VulkanHandle acquire_semaphore_; + std::unique_ptr command_buffer_; + zx::event release_event_; + async_t* async_; + async::WaitMethod wait_; + std::function pending_on_writes_committed_; + size_t age_ = 0; + bool valid_ = false; + + FXL_DISALLOW_COPY_AND_ASSIGN(VulkanSurface); +}; + +} // namespace flutter diff --git a/content_handler/flutter_runner/vulkan_surface_pool.cc b/content_handler/flutter_runner/vulkan_surface_pool.cc new file mode 100644 index 0000000000000..93d14c2ddbff7 --- /dev/null +++ b/content_handler/flutter_runner/vulkan_surface_pool.cc @@ -0,0 +1,181 @@ +// Copyright 2017 The Fuchsia 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 "vulkan_surface_pool.h" + +#include + +#include "flutter/glue/trace_event.h" +#include "third_party/skia/include/gpu/GrContext.h" + +namespace flutter { + +VulkanSurfacePool::VulkanSurfacePool(vulkan::VulkanProvider& vulkan_provider, + sk_sp context, + sk_sp backend_context, + scenic_lib::Session* mozart_session) + : vulkan_provider_(vulkan_provider), + context_(std::move(context)), + backend_context_(std::move(backend_context)), + mozart_session_(mozart_session) {} + +VulkanSurfacePool::~VulkanSurfacePool() {} + +std::unique_ptr +VulkanSurfacePool::AcquireSurface(const SkISize& size) { + auto surface = GetCachedOrCreateSurface(size); + + if (surface == nullptr) { + FXL_DLOG(ERROR) << "Could not acquire surface"; + return nullptr; + } + + if (!surface->FlushSessionAcquireAndReleaseEvents()) { + FXL_DLOG(ERROR) << "Could not flush acquire/release events for buffer."; + return nullptr; + } + + return surface; +} + +std::unique_ptr +VulkanSurfacePool::GetCachedOrCreateSurface(const SkISize& size) { + auto found_in_available = available_surfaces_.find(size); + if (found_in_available == available_surfaces_.end()) { + return CreateSurface(size); + } + SurfacesSet& available_surfaces = found_in_available->second; + FXL_DCHECK(available_surfaces.size() > 0); + auto acquired_surface = std::move(available_surfaces.back()); + available_surfaces.pop_back(); + if (available_surfaces.size() == 0) { + available_surfaces_.erase(found_in_available); + } + if (acquired_surface->IsValid()) { + trace_surfaces_reused_++; + return acquired_surface; + } + // This is only likely to happen if the surface was invalidated between the + // time it was sent into the available buffers cache and accessed now. + // Extremely unlikely. + return CreateSurface(size); +} + +void VulkanSurfacePool::SubmitSurface( + std::unique_ptr + p_surface) { + TRACE_EVENT0("flutter", "VulkanSurfacePool::SubmitSurface"); + if (!p_surface) { + return; + } + + uintptr_t surface_key = reinterpret_cast(p_surface.get()); + + auto insert_iterator = + pending_surfaces_.insert(std::make_pair(surface_key, // key + std::move(p_surface) // value + )); + + if (insert_iterator.second) { + insert_iterator.first->second->SignalWritesFinished( + std::bind(&VulkanSurfacePool::RecycleSurface, this, surface_key)); + } +} + +std::unique_ptr VulkanSurfacePool::CreateSurface( + const SkISize& size) { + auto surface = std::make_unique( + vulkan_provider_, context_, backend_context_, mozart_session_, size); + if (!surface->IsValid()) { + return nullptr; + } + trace_surfaces_created_++; + return surface; +} + +void VulkanSurfacePool::RecycleSurface(uintptr_t surface_key) { + // Before we do anything, we must clear the surface from the collection of + // pending surfaces. + auto found_in_pending = pending_surfaces_.find(surface_key); + if (found_in_pending == pending_surfaces_.end()) { + return; + } + + // Grab a hold of the surface to recycle and clear the entry in the pending + // surfaces collection. + std::unique_ptr + surface_to_recycle = std::move(found_in_pending->second); + pending_surfaces_.erase(found_in_pending); + + // The surface may have become invalid (for example it the fences could + // not be reset). + if (!surface_to_recycle->IsValid()) { + return; + } + + // Recycle the buffer by putting it in the list of available surfaces if the + // maximum size of buffers in the collection is not already present. + auto& available_surfaces = available_surfaces_[surface_to_recycle->GetSize()]; + if (available_surfaces.size() < kMaxSurfacesOfSameSize) { + available_surfaces.emplace_back(std::move(surface_to_recycle)); + } +} + +void VulkanSurfacePool::AgeAndCollectOldBuffers() { + std::vector sizes_to_erase; + for (auto& surface_iterator : available_surfaces_) { + SurfacesSet& old_surfaces = surface_iterator.second; + SurfacesSet new_surfaces; + for (auto& surface : old_surfaces) { + if (surface->AdvanceAndGetAge() < kMaxSurfaceAge) { + new_surfaces.emplace_back(std::move(surface)); + } + } + if (new_surfaces.size() == 0) { + sizes_to_erase.emplace_back(surface_iterator.first); + } + old_surfaces.swap(new_surfaces); + } + for (const auto& size : sizes_to_erase) { + available_surfaces_.erase(size); + } + TraceStats(); +} + +void VulkanSurfacePool::TraceStats() { + // Resources held in cached buffers. + size_t cached_surfaces = 0; + size_t cached_surfaces_bytes = 0; + for (const auto& surfaces : available_surfaces_) { + const auto surface_count = surfaces.second.size(); + cached_surfaces += surface_count; + // TODO(chinmaygarde): Assuming for now that all surfaces are 32bpp. + cached_surfaces_bytes += + (surfaces.first.fWidth * surfaces.first.fHeight * 4 * surface_count); + } + + // Resources held by Skia. + int skia_resources = 0; + size_t skia_bytes = 0; + context_->getResourceCacheUsage(&skia_resources, &skia_bytes); + const size_t skia_cache_purgeable = + context_->getResourceCachePurgeableBytes(); + + TRACE_COUNTER("flutter", "SurfacePool", 0u, // + "CachedCount", cached_surfaces, // + "CachedBytes", cached_surfaces_bytes, // + "Created", trace_surfaces_created_, // + "Reused", trace_surfaces_reused_, // + "PendingInCompositor", pending_surfaces_.size(), // + "SkiaCacheResources", skia_resources, // + "SkiaCacheBytes", skia_bytes, // + "SkiaCachePurgeable", skia_cache_purgeable // + ); + + // Reset per present/frame stats. + trace_surfaces_created_ = 0; + trace_surfaces_reused_ = 0; +} + +} // namespace flutter diff --git a/content_handler/flutter_runner/vulkan_surface_pool.h b/content_handler/flutter_runner/vulkan_surface_pool.h new file mode 100644 index 0000000000000..4900816762910 --- /dev/null +++ b/content_handler/flutter_runner/vulkan_surface_pool.h @@ -0,0 +1,78 @@ +// Copyright 2017 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#pragma once + +#include +#include + +#include "lib/fxl/macros.h" +#include "vulkan_surface.h" + +namespace flutter { + +class VulkanSurfacePool final { + public: + static const size_t kMaxSurfacesOfSameSize = 3; + static const size_t kMaxSurfaceAge = 3; + + VulkanSurfacePool(vulkan::VulkanProvider& vulkan_provider, + sk_sp context, + sk_sp backend_context, + scenic_lib::Session* mozart_session); + + ~VulkanSurfacePool(); + + std::unique_ptr + AcquireSurface(const SkISize& size); + + void SubmitSurface( + std::unique_ptr + surface); + + void AgeAndCollectOldBuffers(); + + private: + using SurfacesSet = std::list< + std::unique_ptr>; + + template + static void HashCombine(size_t& seed, T const& v) { + seed ^= std::hash()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + } + + struct SkISizeHash { + std::size_t operator()(const SkISize& key) const { + size_t seed = 0; + HashCombine(seed, key.fWidth); + HashCombine(seed, key.fHeight); + return seed; + } + }; + + vulkan::VulkanProvider& vulkan_provider_; + sk_sp context_; + sk_sp backend_context_; + scenic_lib::Session* mozart_session_; + std::unordered_map available_surfaces_; + std::unordered_map< + uintptr_t, + std::unique_ptr> + pending_surfaces_; + size_t trace_surfaces_created_ = 0; + size_t trace_surfaces_reused_ = 0; + + std::unique_ptr + GetCachedOrCreateSurface(const SkISize& size); + + std::unique_ptr CreateSurface(const SkISize& size); + + void RecycleSurface(uintptr_t surface_key); + + void TraceStats(); + + FXL_DISALLOW_COPY_AND_ASSIGN(VulkanSurfacePool); +}; + +} // namespace flutter diff --git a/content_handler/flutter_runner/vulkan_surface_producer.cc b/content_handler/flutter_runner/vulkan_surface_producer.cc new file mode 100644 index 0000000000000..df97f7e3110e2 --- /dev/null +++ b/content_handler/flutter_runner/vulkan_surface_producer.cc @@ -0,0 +1,205 @@ +// Copyright 2017 The Chromium 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 "vulkan_surface_producer.h" + +#include +#include +#include + +#include "flutter/glue/trace_event.h" +#include "third_party/skia/include/gpu/GrBackendSemaphore.h" +#include "third_party/skia/include/gpu/GrContext.h" +#include "third_party/skia/include/gpu/vk/GrVkTypes.h" +#include "third_party/skia/src/gpu/vk/GrVkUtil.h" + +namespace flutter { + +VulkanSurfaceProducer::VulkanSurfaceProducer( + scenic_lib::Session* mozart_session) { + valid_ = Initialize(mozart_session); + + if (valid_) { + FXL_LOG(INFO) + << "Flutter engine: Vulkan surface producer initialization: Successful"; + } else { + FXL_LOG(ERROR) + << "Flutter engine: Vulkan surface producer initialization: Failed"; + } +} + +VulkanSurfaceProducer::~VulkanSurfaceProducer() { + // Make sure queue is idle before we start destroying surfaces + VkResult wait_result = + VK_CALL_LOG_ERROR(vk_->QueueWaitIdle(backend_context_->fQueue)); + FXL_DCHECK(wait_result == VK_SUCCESS); +}; + +bool VulkanSurfaceProducer::Initialize(scenic_lib::Session* mozart_session) { + vk_ = fxl::MakeRefCounted(); + + std::vector extensions = { + VK_KHR_SURFACE_EXTENSION_NAME, + VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME, + }; + application_ = std::make_unique( + *vk_, "FlutterApplicationRunner", std::move(extensions)); + + if (!application_->IsValid() || !vk_->AreInstanceProcsSetup()) { + // Make certain the application instance was created and it setup the + // instance proc table entries. + FXL_LOG(ERROR) << "Instance proc addresses have not been setup."; + return false; + } + + // Create the device. + + logical_device_ = application_->AcquireFirstCompatibleLogicalDevice(); + + if (logical_device_ == nullptr || !logical_device_->IsValid() || + !vk_->AreDeviceProcsSetup()) { + // Make certain the device was created and it setup the device proc table + // entries. + FXL_LOG(ERROR) << "Device proc addresses have not been setup."; + return false; + } + + if (!vk_->HasAcquiredMandatoryProcAddresses()) { + FXL_LOG(ERROR) << "Failed to acquire mandatory proc addresses."; + return false; + } + + if (!vk_->IsValid()) { + FXL_LOG(ERROR) << "VulkanProcTable invalid"; + return false; + } + + auto interface = vk_->CreateSkiaInterface(); + + if (interface == nullptr || !interface->validate(0)) { + FXL_LOG(ERROR) << "Skia interface invalid."; + return false; + } + + uint32_t skia_features = 0; + if (!logical_device_->GetPhysicalDeviceFeaturesSkia(&skia_features)) { + FXL_LOG(ERROR) << "Failed to get physical device features."; + + return false; + } + + backend_context_ = sk_make_sp(); + backend_context_->fInstance = application_->GetInstance(); + backend_context_->fPhysicalDevice = + logical_device_->GetPhysicalDeviceHandle(); + backend_context_->fDevice = logical_device_->GetHandle(); + backend_context_->fQueue = logical_device_->GetQueueHandle(); + backend_context_->fGraphicsQueueIndex = + logical_device_->GetGraphicsQueueIndex(); + backend_context_->fMinAPIVersion = application_->GetAPIVersion(); + backend_context_->fFeatures = skia_features; + backend_context_->fInterface.reset(interface.release()); + + logical_device_->ReleaseDeviceOwnership(); + application_->ReleaseInstanceOwnership(); + + context_ = GrContext::MakeVulkan(backend_context_); + + context_->setResourceCacheLimits(vulkan::kGrCacheMaxCount, + vulkan::kGrCacheMaxByteSize); + + surface_pool_ = std::make_unique( + *this, context_, backend_context_, mozart_session); + + return true; +} + +void VulkanSurfaceProducer::OnSurfacesPresented( + std::vector< + std::unique_ptr> + surfaces) { + TRACE_EVENT0("flutter", "VulkanSurfaceProducer::OnSurfacesPresented"); + + // Do a single flush for all canvases derived from the context. + { + TRACE_EVENT0("flutter", "GrContext::flushAndSignalSemaphores"); + context_->flush(); + } + + if (!TransitionSurfacesToExternal(surfaces)) + FXL_LOG(ERROR) << "TransitionSurfacesToExternal failed"; + + // Submit surface + for (auto& surface : surfaces) { + SubmitSurface(std::move(surface)); + } + + // Buffer management. + surface_pool_->AgeAndCollectOldBuffers(); +} + +bool VulkanSurfaceProducer::TransitionSurfacesToExternal( + const std::vector< + std::unique_ptr>& + surfaces) { + for (auto& surface : surfaces) { + auto vk_surface = static_cast(surface.get()); + + vulkan::VulkanCommandBuffer* command_buffer = + vk_surface->GetCommandBuffer(logical_device_->GetCommandPool()); + if (!command_buffer->Begin()) + return false; + + GrVkImageInfo* imageInfo; + vk_surface->GetSkiaSurface()->getRenderTargetHandle( + reinterpret_cast(&imageInfo), + SkSurface::kFlushRead_BackendHandleAccess); + + VkImageMemoryBarrier image_barrier = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .dstAccessMask = 0, + .oldLayout = imageInfo->fImageLayout, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = 0, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL_KHR, + .image = vk_surface->GetVkImage(), + .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}}; + + if (!command_buffer->InsertPipelineBarrier( + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + 0, // dependencyFlags + 0, nullptr, // memory barriers + 0, nullptr, // buffer barriers + 1, &image_barrier)) + return false; + + imageInfo->updateImageLayout(image_barrier.newLayout); + + if (!command_buffer->End()) + return false; + + if (!logical_device_->QueueSubmit( + {}, {}, {vk_surface->GetAcquireVkSemaphore()}, + {command_buffer->Handle()}, vk_surface->GetCommandBufferFence())) + return false; + } + return true; +} + +std::unique_ptr +VulkanSurfaceProducer::ProduceSurface(const SkISize& size) { + FXL_DCHECK(valid_); + return surface_pool_->AcquireSurface(size); +} + +void VulkanSurfaceProducer::SubmitSurface( + std::unique_ptr surface) { + FXL_DCHECK(valid_ && surface != nullptr); + surface_pool_->SubmitSurface(std::move(surface)); +} + +} // namespace flutter diff --git a/content_handler/flutter_runner/vulkan_surface_producer.h b/content_handler/flutter_runner/vulkan_surface_producer.h new file mode 100644 index 0000000000000..15c4419e8cde7 --- /dev/null +++ b/content_handler/flutter_runner/vulkan_surface_producer.h @@ -0,0 +1,77 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#pragma once + +#include "flutter/flow/scene_update_context.h" +#include "flutter/vulkan/vulkan_application.h" +#include "flutter/vulkan/vulkan_device.h" +#include "flutter/vulkan/vulkan_proc_table.h" +#include "flutter/vulkan/vulkan_provider.h" +#include "lib/fsl/tasks/message_loop.h" +#include "lib/fxl/macros.h" +#include "lib/ui/scenic/client/resources.h" +#include "lib/ui/scenic/client/session.h" +#include "third_party/skia/include/gpu/vk/GrVkBackendContext.h" +#include "vulkan_surface.h" +#include "vulkan_surface_pool.h" + +namespace flutter { + +class VulkanSurfaceProducer final + : public flow::SceneUpdateContext::SurfaceProducer, + public vulkan::VulkanProvider { + public: + VulkanSurfaceProducer(scenic_lib::Session* mozart_session); + + ~VulkanSurfaceProducer(); + + bool IsValid() const { return valid_; } + + // |flow::SceneUpdateContext::SurfaceProducer| + std::unique_ptr + ProduceSurface(const SkISize& size) override; + + // |flow::SceneUpdateContext::SurfaceProducer| + void SubmitSurface( + std::unique_ptr surface) + override; + + void OnSurfacesPresented( + std::vector< + std::unique_ptr> + surfaces); + + private: + // VulkanProvider + const vulkan::VulkanProcTable& vk() override { return *vk_.get(); } + const vulkan::VulkanHandle& vk_device() override { + return logical_device_->GetHandle(); + } + + bool TransitionSurfacesToExternal( + const std::vector< + std::unique_ptr>& + surfaces); + + // Note: the order here is very important. The proctable must be destroyed + // last because it contains the function pointers for VkDestroyDevice and + // VkDestroyInstance. The backend context owns the VkDevice and the + // VkInstance, so it must be destroyed after the logical device and the + // application, which own other vulkan objects associated with the device + // and instance. + fxl::RefPtr vk_; + sk_sp backend_context_; + std::unique_ptr logical_device_; + std::unique_ptr application_; + sk_sp context_; + std::unique_ptr surface_pool_; + bool valid_ = false; + + bool Initialize(scenic_lib::Session* mozart_session); + + FXL_DISALLOW_COPY_AND_ASSIGN(VulkanSurfaceProducer); +}; + +} // namespace flutter