mirror of
https://github.com/encounter/engine.git
synced 2026-03-30 11:09:55 -07:00
9675ca2f6b
This reverts commit c2879cae2e.
Additionally, we fix https://github.com/flutter/flutter/issues/40863 by adding a secondary VSYNC callback.
Unit tests are updated to provide VSYNC mocking and check the fix of https://github.com/flutter/flutter/issues/40863.
The root cause of having https://github.com/flutter/flutter/issues/40863 is the false assumption that each input event must trigger a new frame. That was true in the framework PR https://github.com/flutter/flutter/pull/36616 because the input events there are all scrolling move events. When the PR was ported to the engine, we can no longer distinguish different types of events, and tap events may no longer trigger a new frame.
Therefore, this PR directly hooks into the `VsyncWaiter` and uses its (newly added) secondary callback to dispatch the pending input event.
1398 lines
50 KiB
C++
1398 lines
50 KiB
C++
// Copyright 2013 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#define RAPIDJSON_HAS_STDSTRING 1
|
|
#include "flutter/shell/common/shell.h"
|
|
|
|
#include <memory>
|
|
#include <sstream>
|
|
#include <vector>
|
|
|
|
#include "flutter/assets/directory_asset_bundle.h"
|
|
#include "flutter/common/runtime.h"
|
|
#include "flutter/fml/file.h"
|
|
#include "flutter/fml/icu_util.h"
|
|
#include "flutter/fml/log_settings.h"
|
|
#include "flutter/fml/logging.h"
|
|
#include "flutter/fml/make_copyable.h"
|
|
#include "flutter/fml/message_loop.h"
|
|
#include "flutter/fml/paths.h"
|
|
#include "flutter/fml/trace_event.h"
|
|
#include "flutter/fml/unique_fd.h"
|
|
#include "flutter/runtime/dart_vm.h"
|
|
#include "flutter/runtime/start_up.h"
|
|
#include "flutter/shell/common/engine.h"
|
|
#include "flutter/shell/common/persistent_cache.h"
|
|
#include "flutter/shell/common/skia_event_tracer_impl.h"
|
|
#include "flutter/shell/common/switches.h"
|
|
#include "flutter/shell/common/vsync_waiter.h"
|
|
#include "rapidjson/stringbuffer.h"
|
|
#include "rapidjson/writer.h"
|
|
#include "third_party/dart/runtime/include/dart_tools_api.h"
|
|
#include "third_party/skia/include/core/SkGraphics.h"
|
|
#include "third_party/tonic/common/log.h"
|
|
|
|
namespace flutter {
|
|
|
|
constexpr char kSkiaChannel[] = "flutter/skia";
|
|
constexpr char kSystemChannel[] = "flutter/system";
|
|
constexpr char kTypeKey[] = "type";
|
|
constexpr char kFontChange[] = "fontsChange";
|
|
|
|
std::unique_ptr<Shell> Shell::CreateShellOnPlatformThread(
|
|
DartVMRef vm,
|
|
TaskRunners task_runners,
|
|
Settings settings,
|
|
fml::RefPtr<const DartSnapshot> isolate_snapshot,
|
|
fml::RefPtr<const DartSnapshot> shared_snapshot,
|
|
Shell::CreateCallback<PlatformView> on_create_platform_view,
|
|
Shell::CreateCallback<Rasterizer> on_create_rasterizer) {
|
|
if (!task_runners.IsValid()) {
|
|
FML_LOG(ERROR) << "Task runners to run the shell were invalid.";
|
|
return nullptr;
|
|
}
|
|
|
|
auto shell =
|
|
std::unique_ptr<Shell>(new Shell(std::move(vm), task_runners, settings));
|
|
|
|
// Create the rasterizer on the GPU thread.
|
|
std::promise<std::unique_ptr<Rasterizer>> rasterizer_promise;
|
|
auto rasterizer_future = rasterizer_promise.get_future();
|
|
fml::TaskRunner::RunNowOrPostTask(
|
|
task_runners.GetGPUTaskRunner(), [&rasterizer_promise, //
|
|
on_create_rasterizer, //
|
|
shell = shell.get() //
|
|
]() {
|
|
TRACE_EVENT0("flutter", "ShellSetupGPUSubsystem");
|
|
rasterizer_promise.set_value(on_create_rasterizer(*shell));
|
|
});
|
|
|
|
// Create the platform view on the platform thread (this thread).
|
|
auto platform_view = on_create_platform_view(*shell.get());
|
|
if (!platform_view || !platform_view->GetWeakPtr()) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Ask the platform view for the vsync waiter. This will be used by the engine
|
|
// to create the animator.
|
|
auto vsync_waiter = platform_view->CreateVSyncWaiter();
|
|
if (!vsync_waiter) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Create the IO manager on the IO thread. The IO manager must be initialized
|
|
// first because it has state that the other subsystems depend on. It must
|
|
// first be booted and the necessary references obtained to initialize the
|
|
// other subsystems.
|
|
std::promise<std::unique_ptr<ShellIOManager>> io_manager_promise;
|
|
auto io_manager_future = io_manager_promise.get_future();
|
|
std::promise<fml::WeakPtr<ShellIOManager>> weak_io_manager_promise;
|
|
auto weak_io_manager_future = weak_io_manager_promise.get_future();
|
|
auto io_task_runner = shell->GetTaskRunners().GetIOTaskRunner();
|
|
fml::TaskRunner::RunNowOrPostTask(
|
|
io_task_runner,
|
|
[&io_manager_promise, //
|
|
&weak_io_manager_promise, //
|
|
platform_view = platform_view->GetWeakPtr(), //
|
|
io_task_runner //
|
|
]() {
|
|
TRACE_EVENT0("flutter", "ShellSetupIOSubsystem");
|
|
auto io_manager = std::make_unique<ShellIOManager>(
|
|
platform_view->CreateResourceContext(), io_task_runner);
|
|
weak_io_manager_promise.set_value(io_manager->GetWeakPtr());
|
|
io_manager_promise.set_value(std::move(io_manager));
|
|
});
|
|
|
|
// Send dispatcher_maker to the engine constructor because shell won't have
|
|
// platform_view set until Shell::Setup is called later.
|
|
auto dispatcher_maker = platform_view->GetDispatcherMaker();
|
|
|
|
// Create the engine on the UI thread.
|
|
std::promise<std::unique_ptr<Engine>> engine_promise;
|
|
auto engine_future = engine_promise.get_future();
|
|
fml::TaskRunner::RunNowOrPostTask(
|
|
shell->GetTaskRunners().GetUITaskRunner(),
|
|
fml::MakeCopyable([&engine_promise, //
|
|
shell = shell.get(), //
|
|
&dispatcher_maker, //
|
|
isolate_snapshot = std::move(isolate_snapshot), //
|
|
shared_snapshot = std::move(shared_snapshot), //
|
|
vsync_waiter = std::move(vsync_waiter), //
|
|
&weak_io_manager_future //
|
|
]() mutable {
|
|
TRACE_EVENT0("flutter", "ShellSetupUISubsystem");
|
|
const auto& task_runners = shell->GetTaskRunners();
|
|
|
|
// The animator is owned by the UI thread but it gets its vsync pulses
|
|
// from the platform.
|
|
auto animator = std::make_unique<Animator>(*shell, task_runners,
|
|
std::move(vsync_waiter));
|
|
|
|
engine_promise.set_value(std::make_unique<Engine>(
|
|
*shell, //
|
|
dispatcher_maker, //
|
|
*shell->GetDartVM(), //
|
|
std::move(isolate_snapshot), //
|
|
std::move(shared_snapshot), //
|
|
task_runners, //
|
|
shell->GetSettings(), //
|
|
std::move(animator), //
|
|
weak_io_manager_future.get() //
|
|
));
|
|
}));
|
|
|
|
if (!shell->Setup(std::move(platform_view), //
|
|
engine_future.get(), //
|
|
rasterizer_future.get(), //
|
|
io_manager_future.get()) //
|
|
) {
|
|
return nullptr;
|
|
}
|
|
|
|
return shell;
|
|
}
|
|
|
|
static void RecordStartupTimestamp() {
|
|
if (engine_main_enter_ts == 0) {
|
|
engine_main_enter_ts = Dart_TimelineGetMicros();
|
|
}
|
|
}
|
|
|
|
// Though there can be multiple shells, some settings apply to all components in
|
|
// the process. These have to be setup before the shell or any of its
|
|
// sub-components can be initialized. In a perfect world, this would be empty.
|
|
// TODO(chinmaygarde): The unfortunate side effect of this call is that settings
|
|
// that cause shell initialization failures will still lead to some of their
|
|
// settings being applied.
|
|
static void PerformInitializationTasks(const Settings& settings) {
|
|
{
|
|
fml::LogSettings log_settings;
|
|
log_settings.min_log_level =
|
|
settings.verbose_logging ? fml::LOG_INFO : fml::LOG_ERROR;
|
|
fml::SetLogSettings(log_settings);
|
|
}
|
|
|
|
static std::once_flag gShellSettingsInitialization = {};
|
|
std::call_once(gShellSettingsInitialization, [&settings] {
|
|
RecordStartupTimestamp();
|
|
|
|
tonic::SetLogHandler(
|
|
[](const char* message) { FML_LOG(ERROR) << message; });
|
|
|
|
if (settings.trace_skia) {
|
|
InitSkiaEventTracer(settings.trace_skia);
|
|
}
|
|
|
|
if (!settings.skia_deterministic_rendering_on_cpu) {
|
|
SkGraphics::Init();
|
|
} else {
|
|
FML_DLOG(INFO) << "Skia deterministic rendering is enabled.";
|
|
}
|
|
|
|
if (settings.icu_initialization_required) {
|
|
if (settings.icu_data_path.size() != 0) {
|
|
fml::icu::InitializeICU(settings.icu_data_path);
|
|
} else if (settings.icu_mapper) {
|
|
fml::icu::InitializeICUFromMapping(settings.icu_mapper());
|
|
} else {
|
|
FML_DLOG(WARNING) << "Skipping ICU initialization in the shell.";
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
std::unique_ptr<Shell> Shell::Create(
|
|
TaskRunners task_runners,
|
|
Settings settings,
|
|
Shell::CreateCallback<PlatformView> on_create_platform_view,
|
|
Shell::CreateCallback<Rasterizer> on_create_rasterizer) {
|
|
PerformInitializationTasks(settings);
|
|
|
|
TRACE_EVENT0("flutter", "Shell::Create");
|
|
|
|
auto vm = DartVMRef::Create(settings);
|
|
FML_CHECK(vm) << "Must be able to initialize the VM.";
|
|
|
|
auto vm_data = vm->GetVMData();
|
|
|
|
return Shell::Create(std::move(task_runners), //
|
|
std::move(settings), //
|
|
vm_data->GetIsolateSnapshot(), // isolate snapshot
|
|
DartSnapshot::Empty(), // shared snapshot
|
|
std::move(on_create_platform_view), //
|
|
std::move(on_create_rasterizer), //
|
|
std::move(vm) //
|
|
);
|
|
}
|
|
|
|
std::unique_ptr<Shell> Shell::Create(
|
|
TaskRunners task_runners,
|
|
Settings settings,
|
|
fml::RefPtr<const DartSnapshot> isolate_snapshot,
|
|
fml::RefPtr<const DartSnapshot> shared_snapshot,
|
|
Shell::CreateCallback<PlatformView> on_create_platform_view,
|
|
Shell::CreateCallback<Rasterizer> on_create_rasterizer,
|
|
DartVMRef vm) {
|
|
PerformInitializationTasks(settings);
|
|
|
|
TRACE_EVENT0("flutter", "Shell::CreateWithSnapshots");
|
|
|
|
if (!task_runners.IsValid() || !on_create_platform_view ||
|
|
!on_create_rasterizer) {
|
|
return nullptr;
|
|
}
|
|
|
|
fml::AutoResetWaitableEvent latch;
|
|
std::unique_ptr<Shell> shell;
|
|
fml::TaskRunner::RunNowOrPostTask(
|
|
task_runners.GetPlatformTaskRunner(),
|
|
fml::MakeCopyable([&latch, //
|
|
vm = std::move(vm), //
|
|
&shell, //
|
|
task_runners = std::move(task_runners), //
|
|
settings, //
|
|
isolate_snapshot = std::move(isolate_snapshot), //
|
|
shared_snapshot = std::move(shared_snapshot), //
|
|
on_create_platform_view, //
|
|
on_create_rasterizer //
|
|
]() mutable {
|
|
shell = CreateShellOnPlatformThread(std::move(vm),
|
|
std::move(task_runners), //
|
|
settings, //
|
|
std::move(isolate_snapshot), //
|
|
std::move(shared_snapshot), //
|
|
on_create_platform_view, //
|
|
on_create_rasterizer //
|
|
);
|
|
latch.Signal();
|
|
}));
|
|
latch.Wait();
|
|
return shell;
|
|
}
|
|
|
|
Shell::Shell(DartVMRef vm, TaskRunners task_runners, Settings settings)
|
|
: task_runners_(std::move(task_runners)),
|
|
settings_(std::move(settings)),
|
|
vm_(std::move(vm)),
|
|
weak_factory_(this) {
|
|
FML_CHECK(vm_) << "Must have access to VM to create a shell.";
|
|
FML_DCHECK(task_runners_.IsValid());
|
|
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
|
|
|
|
// Install service protocol handlers.
|
|
|
|
service_protocol_handlers_[ServiceProtocol::kScreenshotExtensionName] = {
|
|
task_runners_.GetGPUTaskRunner(),
|
|
std::bind(&Shell::OnServiceProtocolScreenshot, this,
|
|
std::placeholders::_1, std::placeholders::_2)};
|
|
service_protocol_handlers_[ServiceProtocol::kScreenshotSkpExtensionName] = {
|
|
task_runners_.GetGPUTaskRunner(),
|
|
std::bind(&Shell::OnServiceProtocolScreenshotSKP, this,
|
|
std::placeholders::_1, std::placeholders::_2)};
|
|
service_protocol_handlers_[ServiceProtocol::kRunInViewExtensionName] = {
|
|
task_runners_.GetUITaskRunner(),
|
|
std::bind(&Shell::OnServiceProtocolRunInView, this, std::placeholders::_1,
|
|
std::placeholders::_2)};
|
|
service_protocol_handlers_
|
|
[ServiceProtocol::kFlushUIThreadTasksExtensionName] = {
|
|
task_runners_.GetUITaskRunner(),
|
|
std::bind(&Shell::OnServiceProtocolFlushUIThreadTasks, this,
|
|
std::placeholders::_1, std::placeholders::_2)};
|
|
service_protocol_handlers_
|
|
[ServiceProtocol::kSetAssetBundlePathExtensionName] = {
|
|
task_runners_.GetUITaskRunner(),
|
|
std::bind(&Shell::OnServiceProtocolSetAssetBundlePath, this,
|
|
std::placeholders::_1, std::placeholders::_2)};
|
|
service_protocol_handlers_
|
|
[ServiceProtocol::kGetDisplayRefreshRateExtensionName] = {
|
|
task_runners_.GetUITaskRunner(),
|
|
std::bind(&Shell::OnServiceProtocolGetDisplayRefreshRate, this,
|
|
std::placeholders::_1, std::placeholders::_2)};
|
|
}
|
|
|
|
Shell::~Shell() {
|
|
PersistentCache::GetCacheForProcess()->RemoveWorkerTaskRunner(
|
|
task_runners_.GetIOTaskRunner());
|
|
|
|
vm_->GetServiceProtocol()->RemoveHandler(this);
|
|
|
|
fml::AutoResetWaitableEvent ui_latch, gpu_latch, platform_latch, io_latch;
|
|
|
|
fml::TaskRunner::RunNowOrPostTask(
|
|
task_runners_.GetUITaskRunner(),
|
|
fml::MakeCopyable([engine = std::move(engine_), &ui_latch]() mutable {
|
|
engine.reset();
|
|
ui_latch.Signal();
|
|
}));
|
|
ui_latch.Wait();
|
|
|
|
fml::TaskRunner::RunNowOrPostTask(
|
|
task_runners_.GetGPUTaskRunner(),
|
|
fml::MakeCopyable(
|
|
[rasterizer = std::move(rasterizer_), &gpu_latch]() mutable {
|
|
rasterizer.reset();
|
|
gpu_latch.Signal();
|
|
}));
|
|
gpu_latch.Wait();
|
|
|
|
fml::TaskRunner::RunNowOrPostTask(
|
|
task_runners_.GetIOTaskRunner(),
|
|
fml::MakeCopyable([io_manager = std::move(io_manager_),
|
|
platform_view = platform_view_.get(),
|
|
&io_latch]() mutable {
|
|
io_manager.reset();
|
|
if (platform_view) {
|
|
platform_view->ReleaseResourceContext();
|
|
}
|
|
io_latch.Signal();
|
|
}));
|
|
|
|
io_latch.Wait();
|
|
|
|
// The platform view must go last because it may be holding onto platform side
|
|
// counterparts to resources owned by subsystems running on other threads. For
|
|
// example, the NSOpenGLContext on the Mac.
|
|
fml::TaskRunner::RunNowOrPostTask(
|
|
task_runners_.GetPlatformTaskRunner(),
|
|
fml::MakeCopyable([platform_view = std::move(platform_view_),
|
|
&platform_latch]() mutable {
|
|
platform_view.reset();
|
|
platform_latch.Signal();
|
|
}));
|
|
platform_latch.Wait();
|
|
}
|
|
|
|
void Shell::NotifyLowMemoryWarning() const {
|
|
task_runners_.GetGPUTaskRunner()->PostTask(
|
|
[rasterizer = rasterizer_->GetWeakPtr()]() {
|
|
if (rasterizer) {
|
|
rasterizer->NotifyLowMemoryWarning();
|
|
}
|
|
});
|
|
// The IO Manager uses resource cache limits of 0, so it is not necessary
|
|
// to purge them.
|
|
}
|
|
|
|
void Shell::RunEngine(RunConfiguration run_configuration) {
|
|
RunEngine(std::move(run_configuration), nullptr);
|
|
}
|
|
|
|
void Shell::RunEngine(RunConfiguration run_configuration,
|
|
std::function<void(Engine::RunStatus)> result_callback) {
|
|
auto result = [platform_runner = task_runners_.GetPlatformTaskRunner(),
|
|
result_callback](Engine::RunStatus run_result) {
|
|
if (!result_callback) {
|
|
return;
|
|
}
|
|
platform_runner->PostTask(
|
|
[result_callback, run_result]() { result_callback(run_result); });
|
|
};
|
|
FML_DCHECK(is_setup_);
|
|
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
|
|
|
|
if (!weak_engine_) {
|
|
result(Engine::RunStatus::Failure);
|
|
}
|
|
fml::TaskRunner::RunNowOrPostTask(
|
|
task_runners_.GetUITaskRunner(),
|
|
fml::MakeCopyable(
|
|
[run_configuration = std::move(run_configuration),
|
|
weak_engine = weak_engine_, result]() mutable {
|
|
if (!weak_engine) {
|
|
FML_LOG(ERROR)
|
|
<< "Could not launch engine with configuration - no engine.";
|
|
result(Engine::RunStatus::Failure);
|
|
return;
|
|
}
|
|
auto run_result = weak_engine->Run(std::move(run_configuration));
|
|
if (run_result == flutter::Engine::RunStatus::Failure) {
|
|
FML_LOG(ERROR) << "Could not launch engine with configuration.";
|
|
}
|
|
result(run_result);
|
|
}));
|
|
}
|
|
|
|
std::optional<DartErrorCode> Shell::GetUIIsolateLastError() const {
|
|
FML_DCHECK(is_setup_);
|
|
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
|
|
|
|
// We're using the unique_ptr here because we're sure we're on the Platform
|
|
// Thread and callers expect this to be synchronous.
|
|
if (!engine_) {
|
|
return std::nullopt;
|
|
}
|
|
switch (engine_->GetUIIsolateLastError()) {
|
|
case tonic::kCompilationErrorType:
|
|
return DartErrorCode::CompilationError;
|
|
case tonic::kApiErrorType:
|
|
return DartErrorCode::ApiError;
|
|
case tonic::kUnknownErrorType:
|
|
return DartErrorCode::UnknownError;
|
|
case tonic::kNoError:
|
|
return DartErrorCode::NoError;
|
|
}
|
|
return DartErrorCode::UnknownError;
|
|
}
|
|
|
|
bool Shell::EngineHasLivePorts() const {
|
|
FML_DCHECK(is_setup_);
|
|
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
|
|
|
|
// We're using the unique_ptr here because we're sure we're on the Platform
|
|
// Thread and callers expect this to be synchronous.
|
|
if (!engine_) {
|
|
return false;
|
|
}
|
|
return engine_->UIIsolateHasLivePorts();
|
|
}
|
|
|
|
bool Shell::IsSetup() const {
|
|
return is_setup_;
|
|
}
|
|
|
|
bool Shell::Setup(std::unique_ptr<PlatformView> platform_view,
|
|
std::unique_ptr<Engine> engine,
|
|
std::unique_ptr<Rasterizer> rasterizer,
|
|
std::unique_ptr<ShellIOManager> io_manager) {
|
|
if (is_setup_) {
|
|
return false;
|
|
}
|
|
|
|
if (!platform_view || !engine || !rasterizer || !io_manager) {
|
|
return false;
|
|
}
|
|
|
|
platform_view_ = std::move(platform_view);
|
|
engine_ = std::move(engine);
|
|
rasterizer_ = std::move(rasterizer);
|
|
io_manager_ = std::move(io_manager);
|
|
|
|
// The weak ptr must be generated in the platform thread which owns the unique
|
|
// ptr.
|
|
weak_engine_ = engine_->GetWeakPtr();
|
|
weak_rasterizer_ = rasterizer_->GetWeakPtr();
|
|
weak_platform_view_ = platform_view_->GetWeakPtr();
|
|
|
|
is_setup_ = true;
|
|
|
|
vm_->GetServiceProtocol()->AddHandler(this, GetServiceProtocolDescription());
|
|
|
|
PersistentCache::GetCacheForProcess()->AddWorkerTaskRunner(
|
|
task_runners_.GetIOTaskRunner());
|
|
|
|
PersistentCache::GetCacheForProcess()->SetIsDumpingSkp(
|
|
settings_.dump_skp_on_shader_compilation);
|
|
|
|
// Shell::Setup is running on the UI thread so we can do the following.
|
|
display_refresh_rate_ = weak_engine_->GetDisplayRefreshRate();
|
|
|
|
return true;
|
|
}
|
|
|
|
const Settings& Shell::GetSettings() const {
|
|
return settings_;
|
|
}
|
|
|
|
const TaskRunners& Shell::GetTaskRunners() const {
|
|
return task_runners_;
|
|
}
|
|
|
|
fml::WeakPtr<Rasterizer> Shell::GetRasterizer() {
|
|
FML_DCHECK(is_setup_);
|
|
return weak_rasterizer_;
|
|
}
|
|
|
|
// TODO(dnfield): Remove this when either Topaz is up to date or flutter_runner
|
|
// is built out of this repo.
|
|
#ifdef OS_FUCHSIA
|
|
fml::WeakPtr<Engine> Shell::GetEngine() {
|
|
FML_DCHECK(is_setup_);
|
|
return weak_engine_;
|
|
}
|
|
#endif // OS_FUCHSIA
|
|
|
|
fml::WeakPtr<PlatformView> Shell::GetPlatformView() {
|
|
FML_DCHECK(is_setup_);
|
|
return weak_platform_view_;
|
|
}
|
|
|
|
DartVM* Shell::GetDartVM() {
|
|
return &vm_;
|
|
}
|
|
|
|
// |PlatformView::Delegate|
|
|
void Shell::OnPlatformViewCreated(std::unique_ptr<Surface> surface) {
|
|
TRACE_EVENT0("flutter", "Shell::OnPlatformViewCreated");
|
|
FML_DCHECK(is_setup_);
|
|
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
|
|
|
|
// Note:
|
|
// This is a synchronous operation because certain platforms depend on
|
|
// setup/suspension of all activities that may be interacting with the GPU in
|
|
// a synchronous fashion.
|
|
fml::AutoResetWaitableEvent latch;
|
|
auto gpu_task =
|
|
fml::MakeCopyable([& waiting_for_first_frame = waiting_for_first_frame_,
|
|
rasterizer = rasterizer_->GetWeakPtr(), //
|
|
surface = std::move(surface), //
|
|
&latch]() mutable {
|
|
if (rasterizer) {
|
|
rasterizer->Setup(std::move(surface));
|
|
}
|
|
|
|
waiting_for_first_frame.store(true);
|
|
|
|
// Step 3: All done. Signal the latch that the platform thread is
|
|
// waiting on.
|
|
latch.Signal();
|
|
});
|
|
|
|
// The normal flow executed by this method is that the platform thread is
|
|
// starting the sequence and waiting on the latch. Later the UI thread posts
|
|
// gpu_task to the GPU thread which signals the latch. If the GPU the and
|
|
// platform threads are the same this results in a deadlock as the gpu_task
|
|
// will never be posted to the plaform/gpu thread that is blocked on a latch.
|
|
// To avoid the described deadlock, if the gpu and the platform threads are
|
|
// the same, should_post_gpu_task will be false, and then instead of posting a
|
|
// task to the gpu thread, the ui thread just signals the latch and the
|
|
// platform/gpu thread follows with executing gpu_task.
|
|
bool should_post_gpu_task =
|
|
task_runners_.GetGPUTaskRunner() != task_runners_.GetPlatformTaskRunner();
|
|
|
|
auto ui_task = [engine = engine_->GetWeakPtr(), //
|
|
gpu_task_runner = task_runners_.GetGPUTaskRunner(), //
|
|
gpu_task, should_post_gpu_task,
|
|
&latch //
|
|
] {
|
|
if (engine) {
|
|
engine->OnOutputSurfaceCreated();
|
|
}
|
|
// Step 2: Next, tell the GPU thread that it should create a surface for its
|
|
// rasterizer.
|
|
if (should_post_gpu_task) {
|
|
fml::TaskRunner::RunNowOrPostTask(gpu_task_runner, gpu_task);
|
|
} else {
|
|
// See comment on should_post_gpu_task, in this case we just unblock
|
|
// the platform thread.
|
|
latch.Signal();
|
|
}
|
|
};
|
|
|
|
// Threading: Capture platform view by raw pointer and not the weak pointer.
|
|
// We are going to use the pointer on the IO thread which is not safe with a
|
|
// weak pointer. However, we are preventing the platform view from being
|
|
// collected by using a latch.
|
|
auto* platform_view = platform_view_.get();
|
|
|
|
FML_DCHECK(platform_view);
|
|
|
|
auto io_task = [io_manager = io_manager_->GetWeakPtr(), platform_view,
|
|
ui_task_runner = task_runners_.GetUITaskRunner(), ui_task] {
|
|
if (io_manager && !io_manager->GetResourceContext()) {
|
|
io_manager->NotifyResourceContextAvailable(
|
|
platform_view->CreateResourceContext());
|
|
}
|
|
// Step 1: Next, post a task on the UI thread to tell the engine that it has
|
|
// an output surface.
|
|
fml::TaskRunner::RunNowOrPostTask(ui_task_runner, ui_task);
|
|
};
|
|
|
|
fml::TaskRunner::RunNowOrPostTask(task_runners_.GetIOTaskRunner(), io_task);
|
|
|
|
latch.Wait();
|
|
if (!should_post_gpu_task) {
|
|
// See comment on should_post_gpu_task, in this case the gpu_task
|
|
// wasn't executed, and we just run it here as the platform thread
|
|
// is the GPU thread.
|
|
gpu_task();
|
|
}
|
|
}
|
|
|
|
// |PlatformView::Delegate|
|
|
void Shell::OnPlatformViewDestroyed() {
|
|
TRACE_EVENT0("flutter", "Shell::OnPlatformViewDestroyed");
|
|
FML_DCHECK(is_setup_);
|
|
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
|
|
|
|
// Note:
|
|
// This is a synchronous operation because certain platforms depend on
|
|
// setup/suspension of all activities that may be interacting with the GPU in
|
|
// a synchronous fashion.
|
|
|
|
fml::AutoResetWaitableEvent latch;
|
|
|
|
auto io_task = [io_manager = io_manager_.get(), &latch]() {
|
|
// Execute any pending Skia object deletions while GPU access is still
|
|
// allowed.
|
|
io_manager->GetSkiaUnrefQueue()->Drain();
|
|
// Step 3: All done. Signal the latch that the platform thread is waiting
|
|
// on.
|
|
latch.Signal();
|
|
};
|
|
|
|
auto gpu_task = [rasterizer = rasterizer_->GetWeakPtr(),
|
|
io_task_runner = task_runners_.GetIOTaskRunner(),
|
|
io_task]() {
|
|
if (rasterizer) {
|
|
rasterizer->Teardown();
|
|
}
|
|
// Step 2: Next, tell the IO thread to complete its remaining work.
|
|
fml::TaskRunner::RunNowOrPostTask(io_task_runner, io_task);
|
|
};
|
|
|
|
// The normal flow executed by this method is that the platform thread is
|
|
// starting the sequence and waiting on the latch. Later the UI thread posts
|
|
// gpu_task to the GPU thread triggers signaling the latch(on the IO thread).
|
|
// If the GPU the and platform threads are the same this results in a deadlock
|
|
// as the gpu_task will never be posted to the plaform/gpu thread that is
|
|
// blocked on a latch. To avoid the described deadlock, if the gpu and the
|
|
// platform threads are the same, should_post_gpu_task will be false, and then
|
|
// instead of posting a task to the gpu thread, the ui thread just signals the
|
|
// latch and the platform/gpu thread follows with executing gpu_task.
|
|
bool should_post_gpu_task =
|
|
task_runners_.GetGPUTaskRunner() != task_runners_.GetPlatformTaskRunner();
|
|
|
|
auto ui_task = [engine = engine_->GetWeakPtr(),
|
|
gpu_task_runner = task_runners_.GetGPUTaskRunner(), gpu_task,
|
|
should_post_gpu_task, &latch]() {
|
|
if (engine) {
|
|
engine->OnOutputSurfaceDestroyed();
|
|
}
|
|
// Step 1: Next, tell the GPU thread that its rasterizer should suspend
|
|
// access to the underlying surface.
|
|
if (should_post_gpu_task) {
|
|
fml::TaskRunner::RunNowOrPostTask(gpu_task_runner, gpu_task);
|
|
} else {
|
|
// See comment on should_post_gpu_task, in this case we just unblock
|
|
// the platform thread.
|
|
latch.Signal();
|
|
}
|
|
};
|
|
|
|
// Step 0: Post a task onto the UI thread to tell the engine that its output
|
|
// surface is about to go away.
|
|
fml::TaskRunner::RunNowOrPostTask(task_runners_.GetUITaskRunner(), ui_task);
|
|
latch.Wait();
|
|
if (!should_post_gpu_task) {
|
|
// See comment on should_post_gpu_task, in this case the gpu_task
|
|
// wasn't executed, and we just run it here as the platform thread
|
|
// is the GPU thread.
|
|
gpu_task();
|
|
latch.Wait();
|
|
}
|
|
}
|
|
|
|
// |PlatformView::Delegate|
|
|
void Shell::OnPlatformViewSetViewportMetrics(const ViewportMetrics& metrics) {
|
|
FML_DCHECK(is_setup_);
|
|
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
|
|
|
|
// This is the formula Android uses.
|
|
// https://android.googlesource.com/platform/frameworks/base/+/master/libs/hwui/renderthread/CacheManager.cpp#41
|
|
size_t max_bytes = metrics.physical_width * metrics.physical_height * 12 * 4;
|
|
task_runners_.GetGPUTaskRunner()->PostTask(
|
|
[rasterizer = rasterizer_->GetWeakPtr(), max_bytes] {
|
|
if (rasterizer) {
|
|
rasterizer->SetResourceCacheMaxBytes(max_bytes, false);
|
|
}
|
|
});
|
|
|
|
task_runners_.GetUITaskRunner()->PostTask(
|
|
[engine = engine_->GetWeakPtr(), metrics]() {
|
|
if (engine) {
|
|
engine->SetViewportMetrics(metrics);
|
|
}
|
|
});
|
|
}
|
|
|
|
// |PlatformView::Delegate|
|
|
void Shell::OnPlatformViewDispatchPlatformMessage(
|
|
fml::RefPtr<PlatformMessage> message) {
|
|
FML_DCHECK(is_setup_);
|
|
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
|
|
|
|
task_runners_.GetUITaskRunner()->PostTask(
|
|
[engine = engine_->GetWeakPtr(), message = std::move(message)] {
|
|
if (engine) {
|
|
engine->DispatchPlatformMessage(std::move(message));
|
|
}
|
|
});
|
|
}
|
|
|
|
// |PlatformView::Delegate|
|
|
void Shell::OnPlatformViewDispatchPointerDataPacket(
|
|
std::unique_ptr<PointerDataPacket> packet) {
|
|
TRACE_EVENT0("flutter", "Shell::OnPlatformViewDispatchPointerDataPacket");
|
|
TRACE_FLOW_BEGIN("flutter", "PointerEvent", next_pointer_flow_id_);
|
|
FML_DCHECK(is_setup_);
|
|
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
|
|
task_runners_.GetUITaskRunner()->PostTask(
|
|
fml::MakeCopyable([engine = weak_engine_, packet = std::move(packet),
|
|
flow_id = next_pointer_flow_id_]() mutable {
|
|
if (engine) {
|
|
engine->DispatchPointerDataPacket(std::move(packet), flow_id);
|
|
}
|
|
}));
|
|
next_pointer_flow_id_++;
|
|
}
|
|
|
|
// |PlatformView::Delegate|
|
|
void Shell::OnPlatformViewDispatchSemanticsAction(int32_t id,
|
|
SemanticsAction action,
|
|
std::vector<uint8_t> args) {
|
|
FML_DCHECK(is_setup_);
|
|
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
|
|
|
|
task_runners_.GetUITaskRunner()->PostTask(
|
|
[engine = engine_->GetWeakPtr(), id, action, args = std::move(args)] {
|
|
if (engine) {
|
|
engine->DispatchSemanticsAction(id, action, std::move(args));
|
|
}
|
|
});
|
|
}
|
|
|
|
// |PlatformView::Delegate|
|
|
void Shell::OnPlatformViewSetSemanticsEnabled(bool enabled) {
|
|
FML_DCHECK(is_setup_);
|
|
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
|
|
|
|
task_runners_.GetUITaskRunner()->PostTask(
|
|
[engine = engine_->GetWeakPtr(), enabled] {
|
|
if (engine) {
|
|
engine->SetSemanticsEnabled(enabled);
|
|
}
|
|
});
|
|
}
|
|
|
|
// |PlatformView::Delegate|
|
|
void Shell::OnPlatformViewSetAccessibilityFeatures(int32_t flags) {
|
|
FML_DCHECK(is_setup_);
|
|
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
|
|
|
|
task_runners_.GetUITaskRunner()->PostTask(
|
|
[engine = engine_->GetWeakPtr(), flags] {
|
|
if (engine) {
|
|
engine->SetAccessibilityFeatures(flags);
|
|
}
|
|
});
|
|
}
|
|
|
|
// |PlatformView::Delegate|
|
|
void Shell::OnPlatformViewRegisterTexture(
|
|
std::shared_ptr<flutter::Texture> texture) {
|
|
FML_DCHECK(is_setup_);
|
|
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
|
|
|
|
task_runners_.GetGPUTaskRunner()->PostTask(
|
|
[rasterizer = rasterizer_->GetWeakPtr(), texture] {
|
|
if (rasterizer) {
|
|
if (auto* registry = rasterizer->GetTextureRegistry()) {
|
|
registry->RegisterTexture(texture);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// |PlatformView::Delegate|
|
|
void Shell::OnPlatformViewUnregisterTexture(int64_t texture_id) {
|
|
FML_DCHECK(is_setup_);
|
|
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
|
|
|
|
task_runners_.GetGPUTaskRunner()->PostTask(
|
|
[rasterizer = rasterizer_->GetWeakPtr(), texture_id]() {
|
|
if (rasterizer) {
|
|
if (auto* registry = rasterizer->GetTextureRegistry()) {
|
|
registry->UnregisterTexture(texture_id);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// |PlatformView::Delegate|
|
|
void Shell::OnPlatformViewMarkTextureFrameAvailable(int64_t texture_id) {
|
|
FML_DCHECK(is_setup_);
|
|
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
|
|
|
|
// Tell the rasterizer that one of its textures has a new frame available.
|
|
task_runners_.GetGPUTaskRunner()->PostTask(
|
|
[rasterizer = rasterizer_->GetWeakPtr(), texture_id]() {
|
|
auto* registry = rasterizer->GetTextureRegistry();
|
|
|
|
if (!registry) {
|
|
return;
|
|
}
|
|
|
|
auto texture = registry->GetTexture(texture_id);
|
|
|
|
if (!texture) {
|
|
return;
|
|
}
|
|
|
|
texture->MarkNewFrameAvailable();
|
|
});
|
|
|
|
// Schedule a new frame without having to rebuild the layer tree.
|
|
task_runners_.GetUITaskRunner()->PostTask([engine = engine_->GetWeakPtr()]() {
|
|
if (engine) {
|
|
engine->ScheduleFrame(false);
|
|
}
|
|
});
|
|
}
|
|
|
|
// |PlatformView::Delegate|
|
|
void Shell::OnPlatformViewSetNextFrameCallback(fml::closure closure) {
|
|
FML_DCHECK(is_setup_);
|
|
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
|
|
|
|
task_runners_.GetGPUTaskRunner()->PostTask(
|
|
[rasterizer = rasterizer_->GetWeakPtr(), closure = std::move(closure)]() {
|
|
if (rasterizer) {
|
|
rasterizer->SetNextFrameCallback(std::move(closure));
|
|
}
|
|
});
|
|
}
|
|
|
|
// |Animator::Delegate|
|
|
void Shell::OnAnimatorBeginFrame(fml::TimePoint frame_time) {
|
|
FML_DCHECK(is_setup_);
|
|
FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
|
|
|
|
if (engine_) {
|
|
engine_->BeginFrame(frame_time);
|
|
}
|
|
}
|
|
|
|
// |Animator::Delegate|
|
|
void Shell::OnAnimatorNotifyIdle(int64_t deadline) {
|
|
FML_DCHECK(is_setup_);
|
|
FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
|
|
|
|
if (engine_) {
|
|
engine_->NotifyIdle(deadline);
|
|
}
|
|
}
|
|
|
|
// |Animator::Delegate|
|
|
void Shell::OnAnimatorDraw(fml::RefPtr<Pipeline<flutter::LayerTree>> pipeline) {
|
|
FML_DCHECK(is_setup_);
|
|
|
|
task_runners_.GetGPUTaskRunner()->PostTask(
|
|
[& waiting_for_first_frame = waiting_for_first_frame_,
|
|
&waiting_for_first_frame_condition = waiting_for_first_frame_condition_,
|
|
rasterizer = rasterizer_->GetWeakPtr(),
|
|
pipeline = std::move(pipeline)]() {
|
|
if (rasterizer) {
|
|
rasterizer->Draw(pipeline);
|
|
|
|
if (waiting_for_first_frame.load()) {
|
|
waiting_for_first_frame.store(false);
|
|
waiting_for_first_frame_condition.notify_all();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// |Animator::Delegate|
|
|
void Shell::OnAnimatorDrawLastLayerTree() {
|
|
FML_DCHECK(is_setup_);
|
|
|
|
task_runners_.GetGPUTaskRunner()->PostTask(
|
|
[rasterizer = rasterizer_->GetWeakPtr()]() {
|
|
if (rasterizer) {
|
|
rasterizer->DrawLastLayerTree();
|
|
}
|
|
});
|
|
}
|
|
|
|
// |Engine::Delegate|
|
|
void Shell::OnEngineUpdateSemantics(SemanticsNodeUpdates update,
|
|
CustomAccessibilityActionUpdates actions) {
|
|
FML_DCHECK(is_setup_);
|
|
FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
|
|
|
|
task_runners_.GetPlatformTaskRunner()->PostTask(
|
|
[view = platform_view_->GetWeakPtr(), update = std::move(update),
|
|
actions = std::move(actions)] {
|
|
if (view) {
|
|
view->UpdateSemantics(std::move(update), std::move(actions));
|
|
}
|
|
});
|
|
}
|
|
|
|
// |Engine::Delegate|
|
|
void Shell::OnEngineHandlePlatformMessage(
|
|
fml::RefPtr<PlatformMessage> message) {
|
|
FML_DCHECK(is_setup_);
|
|
FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
|
|
|
|
if (message->channel() == kSkiaChannel) {
|
|
HandleEngineSkiaMessage(std::move(message));
|
|
return;
|
|
}
|
|
|
|
task_runners_.GetPlatformTaskRunner()->PostTask(
|
|
[view = platform_view_->GetWeakPtr(), message = std::move(message)]() {
|
|
if (view) {
|
|
view->HandlePlatformMessage(std::move(message));
|
|
}
|
|
});
|
|
}
|
|
|
|
void Shell::HandleEngineSkiaMessage(fml::RefPtr<PlatformMessage> message) {
|
|
const auto& data = message->data();
|
|
|
|
rapidjson::Document document;
|
|
document.Parse(reinterpret_cast<const char*>(data.data()), data.size());
|
|
if (document.HasParseError() || !document.IsObject())
|
|
return;
|
|
auto root = document.GetObject();
|
|
auto method = root.FindMember("method");
|
|
if (method->value != "Skia.setResourceCacheMaxBytes")
|
|
return;
|
|
auto args = root.FindMember("args");
|
|
if (args == root.MemberEnd() || !args->value.IsInt())
|
|
return;
|
|
|
|
task_runners_.GetGPUTaskRunner()->PostTask(
|
|
[rasterizer = rasterizer_->GetWeakPtr(), max_bytes = args->value.GetInt(),
|
|
response = std::move(message->response())] {
|
|
if (rasterizer) {
|
|
rasterizer->SetResourceCacheMaxBytes(static_cast<size_t>(max_bytes),
|
|
true);
|
|
}
|
|
if (response) {
|
|
// The framework side expects this to be valid json encoded as a list.
|
|
// Return `[true]` to signal success.
|
|
std::vector<uint8_t> data = {'[', 't', 'r', 'u', 'e', ']'};
|
|
response->Complete(
|
|
std::make_unique<fml::DataMapping>(std::move(data)));
|
|
}
|
|
});
|
|
}
|
|
|
|
// |Engine::Delegate|
|
|
void Shell::OnPreEngineRestart() {
|
|
FML_DCHECK(is_setup_);
|
|
FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
|
|
|
|
fml::AutoResetWaitableEvent latch;
|
|
fml::TaskRunner::RunNowOrPostTask(
|
|
task_runners_.GetPlatformTaskRunner(),
|
|
[view = platform_view_->GetWeakPtr(), &latch]() {
|
|
if (view) {
|
|
view->OnPreEngineRestart();
|
|
}
|
|
latch.Signal();
|
|
});
|
|
// This is blocking as any embedded platform views has to be flushed before
|
|
// we re-run the Dart code.
|
|
latch.Wait();
|
|
}
|
|
|
|
// |Engine::Delegate|
|
|
void Shell::UpdateIsolateDescription(const std::string isolate_name,
|
|
int64_t isolate_port) {
|
|
Handler::Description description(isolate_port, isolate_name);
|
|
vm_->GetServiceProtocol()->SetHandlerDescription(this, description);
|
|
}
|
|
|
|
void Shell::SetNeedsReportTimings(bool value) {
|
|
needs_report_timings_ = value;
|
|
}
|
|
|
|
void Shell::ReportTimings() {
|
|
FML_DCHECK(is_setup_);
|
|
FML_DCHECK(task_runners_.GetGPUTaskRunner()->RunsTasksOnCurrentThread());
|
|
|
|
auto timings = std::move(unreported_timings_);
|
|
unreported_timings_ = {};
|
|
task_runners_.GetUITaskRunner()->PostTask([timings, engine = weak_engine_] {
|
|
if (engine) {
|
|
engine->ReportTimings(std::move(timings));
|
|
}
|
|
});
|
|
}
|
|
|
|
size_t Shell::UnreportedFramesCount() const {
|
|
// Check that this is running on the GPU thread to avoid race conditions.
|
|
FML_DCHECK(task_runners_.GetGPUTaskRunner()->RunsTasksOnCurrentThread());
|
|
FML_DCHECK(unreported_timings_.size() % FrameTiming::kCount == 0);
|
|
return unreported_timings_.size() / FrameTiming::kCount;
|
|
}
|
|
|
|
void Shell::OnFrameRasterized(const FrameTiming& timing) {
|
|
FML_DCHECK(is_setup_);
|
|
FML_DCHECK(task_runners_.GetGPUTaskRunner()->RunsTasksOnCurrentThread());
|
|
|
|
// The C++ callback defined in settings.h and set by Flutter runner. This is
|
|
// independent of the timings report to the Dart side.
|
|
if (settings_.frame_rasterized_callback) {
|
|
settings_.frame_rasterized_callback(timing);
|
|
}
|
|
|
|
if (!needs_report_timings_) {
|
|
return;
|
|
}
|
|
|
|
for (auto phase : FrameTiming::kPhases) {
|
|
unreported_timings_.push_back(
|
|
timing.Get(phase).ToEpochDelta().ToMicroseconds());
|
|
}
|
|
|
|
// In tests using iPhone 6S with profile mode, sending a batch of 1 frame or a
|
|
// batch of 100 frames have roughly the same cost of less than 0.1ms. Sending
|
|
// a batch of 500 frames costs about 0.2ms. The 1 second threshold usually
|
|
// kicks in before we reaching the following 100 frames threshold. The 100
|
|
// threshold here is mainly for unit tests (so we don't have to write a
|
|
// 1-second unit test), and make sure that our vector won't grow too big with
|
|
// future 120fps, 240fps, or 1000fps displays.
|
|
//
|
|
// In the profile/debug mode, the timings are used by development tools which
|
|
// require a latency of no more than 100ms. Hence we lower that 1-second
|
|
// threshold to 100ms because performance overhead isn't that critical in
|
|
// those cases.
|
|
if (!first_frame_rasterized_ || UnreportedFramesCount() >= 100) {
|
|
first_frame_rasterized_ = true;
|
|
ReportTimings();
|
|
} else if (!frame_timings_report_scheduled_) {
|
|
#if FLUTTER_RELEASE
|
|
constexpr int kBatchTimeInMilliseconds = 1000;
|
|
#else
|
|
constexpr int kBatchTimeInMilliseconds = 100;
|
|
#endif
|
|
|
|
// Also make sure that frame times get reported with a max latency of 1
|
|
// second. Otherwise, the timings of last few frames of an animation may
|
|
// never be reported until the next animation starts.
|
|
frame_timings_report_scheduled_ = true;
|
|
task_runners_.GetGPUTaskRunner()->PostDelayedTask(
|
|
[self = weak_factory_.GetWeakPtr()]() {
|
|
if (!self.get()) {
|
|
return;
|
|
}
|
|
self->frame_timings_report_scheduled_ = false;
|
|
if (self->UnreportedFramesCount() > 0) {
|
|
self->ReportTimings();
|
|
}
|
|
},
|
|
fml::TimeDelta::FromMilliseconds(kBatchTimeInMilliseconds));
|
|
}
|
|
}
|
|
|
|
fml::Milliseconds Shell::GetFrameBudget() {
|
|
if (display_refresh_rate_ > 0) {
|
|
return fml::RefreshRateToFrameBudget(display_refresh_rate_.load());
|
|
} else {
|
|
return fml::kDefaultFrameBudget;
|
|
}
|
|
}
|
|
|
|
// |ServiceProtocol::Handler|
|
|
fml::RefPtr<fml::TaskRunner> Shell::GetServiceProtocolHandlerTaskRunner(
|
|
std::string_view method) const {
|
|
FML_DCHECK(is_setup_);
|
|
auto found = service_protocol_handlers_.find(method);
|
|
if (found != service_protocol_handlers_.end()) {
|
|
return found->second.first;
|
|
}
|
|
return task_runners_.GetUITaskRunner();
|
|
}
|
|
|
|
// |ServiceProtocol::Handler|
|
|
bool Shell::HandleServiceProtocolMessage(
|
|
std::string_view method, // one if the extension names specified above.
|
|
const ServiceProtocolMap& params,
|
|
rapidjson::Document& response) {
|
|
auto found = service_protocol_handlers_.find(method);
|
|
if (found != service_protocol_handlers_.end()) {
|
|
return found->second.second(params, response);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// |ServiceProtocol::Handler|
|
|
ServiceProtocol::Handler::Description Shell::GetServiceProtocolDescription()
|
|
const {
|
|
return {
|
|
engine_->GetUIIsolateMainPort(),
|
|
engine_->GetUIIsolateName(),
|
|
};
|
|
}
|
|
|
|
static void ServiceProtocolParameterError(rapidjson::Document& response,
|
|
std::string error_details) {
|
|
auto& allocator = response.GetAllocator();
|
|
response.SetObject();
|
|
const int64_t kInvalidParams = -32602;
|
|
response.AddMember("code", kInvalidParams, allocator);
|
|
response.AddMember("message", "Invalid params", allocator);
|
|
{
|
|
rapidjson::Value details(rapidjson::kObjectType);
|
|
details.AddMember("details", error_details, allocator);
|
|
response.AddMember("data", details, allocator);
|
|
}
|
|
}
|
|
|
|
static void ServiceProtocolFailureError(rapidjson::Document& response,
|
|
std::string message) {
|
|
auto& allocator = response.GetAllocator();
|
|
response.SetObject();
|
|
const int64_t kJsonServerError = -32000;
|
|
response.AddMember("code", kJsonServerError, allocator);
|
|
response.AddMember("message", message, allocator);
|
|
}
|
|
|
|
// Service protocol handler
|
|
bool Shell::OnServiceProtocolScreenshot(
|
|
const ServiceProtocol::Handler::ServiceProtocolMap& params,
|
|
rapidjson::Document& response) {
|
|
FML_DCHECK(task_runners_.GetGPUTaskRunner()->RunsTasksOnCurrentThread());
|
|
auto screenshot = rasterizer_->ScreenshotLastLayerTree(
|
|
Rasterizer::ScreenshotType::CompressedImage, true);
|
|
if (screenshot.data) {
|
|
response.SetObject();
|
|
auto& allocator = response.GetAllocator();
|
|
response.AddMember("type", "Screenshot", allocator);
|
|
rapidjson::Value image;
|
|
image.SetString(static_cast<const char*>(screenshot.data->data()),
|
|
screenshot.data->size(), allocator);
|
|
response.AddMember("screenshot", image, allocator);
|
|
return true;
|
|
}
|
|
ServiceProtocolFailureError(response, "Could not capture image screenshot.");
|
|
return false;
|
|
}
|
|
|
|
// Service protocol handler
|
|
bool Shell::OnServiceProtocolScreenshotSKP(
|
|
const ServiceProtocol::Handler::ServiceProtocolMap& params,
|
|
rapidjson::Document& response) {
|
|
FML_DCHECK(task_runners_.GetGPUTaskRunner()->RunsTasksOnCurrentThread());
|
|
auto screenshot = rasterizer_->ScreenshotLastLayerTree(
|
|
Rasterizer::ScreenshotType::SkiaPicture, true);
|
|
if (screenshot.data) {
|
|
response.SetObject();
|
|
auto& allocator = response.GetAllocator();
|
|
response.AddMember("type", "ScreenshotSkp", allocator);
|
|
rapidjson::Value skp;
|
|
skp.SetString(static_cast<const char*>(screenshot.data->data()),
|
|
screenshot.data->size(), allocator);
|
|
response.AddMember("skp", skp, allocator);
|
|
return true;
|
|
}
|
|
ServiceProtocolFailureError(response, "Could not capture SKP screenshot.");
|
|
return false;
|
|
}
|
|
|
|
// Service protocol handler
|
|
bool Shell::OnServiceProtocolRunInView(
|
|
const ServiceProtocol::Handler::ServiceProtocolMap& params,
|
|
rapidjson::Document& response) {
|
|
FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
|
|
|
|
if (params.count("mainScript") == 0) {
|
|
ServiceProtocolParameterError(response,
|
|
"'mainScript' parameter is missing.");
|
|
return false;
|
|
}
|
|
|
|
// TODO(chinmaygarde): In case of hot-reload from .dill files, the packages
|
|
// file is ignored. Currently, the tool is passing a junk packages file to
|
|
// pass this check. Update the service protocol interface and remove this
|
|
// workaround.
|
|
if (params.count("packagesFile") == 0) {
|
|
ServiceProtocolParameterError(response,
|
|
"'packagesFile' parameter is missing.");
|
|
return false;
|
|
}
|
|
|
|
if (params.count("assetDirectory") == 0) {
|
|
ServiceProtocolParameterError(response,
|
|
"'assetDirectory' parameter is missing.");
|
|
return false;
|
|
}
|
|
|
|
std::string main_script_path =
|
|
fml::paths::FromURI(params.at("mainScript").data());
|
|
std::string packages_path =
|
|
fml::paths::FromURI(params.at("packagesFile").data());
|
|
std::string asset_directory_path =
|
|
fml::paths::FromURI(params.at("assetDirectory").data());
|
|
|
|
auto main_script_file_mapping =
|
|
std::make_unique<fml::FileMapping>(fml::OpenFile(
|
|
main_script_path.c_str(), false, fml::FilePermission::kRead));
|
|
|
|
auto isolate_configuration = IsolateConfiguration::CreateForKernel(
|
|
std::move(main_script_file_mapping));
|
|
|
|
RunConfiguration configuration(std::move(isolate_configuration));
|
|
|
|
configuration.AddAssetResolver(
|
|
std::make_unique<DirectoryAssetBundle>(fml::OpenDirectory(
|
|
asset_directory_path.c_str(), false, fml::FilePermission::kRead)));
|
|
|
|
auto& allocator = response.GetAllocator();
|
|
response.SetObject();
|
|
if (engine_->Restart(std::move(configuration))) {
|
|
response.AddMember("type", "Success", allocator);
|
|
auto new_description = GetServiceProtocolDescription();
|
|
rapidjson::Value view(rapidjson::kObjectType);
|
|
new_description.Write(this, view, allocator);
|
|
response.AddMember("view", view, allocator);
|
|
return true;
|
|
} else {
|
|
FML_DLOG(ERROR) << "Could not run configuration in engine.";
|
|
ServiceProtocolFailureError(response,
|
|
"Could not run configuration in engine.");
|
|
return false;
|
|
}
|
|
|
|
FML_DCHECK(false);
|
|
return false;
|
|
}
|
|
|
|
// Service protocol handler
|
|
bool Shell::OnServiceProtocolFlushUIThreadTasks(
|
|
const ServiceProtocol::Handler::ServiceProtocolMap& params,
|
|
rapidjson::Document& response) {
|
|
FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
|
|
// This API should not be invoked by production code.
|
|
// It can potentially starve the service isolate if the main isolate pauses
|
|
// at a breakpoint or is in an infinite loop.
|
|
//
|
|
// It should be invoked from the VM Service and and blocks it until UI thread
|
|
// tasks are processed.
|
|
response.SetObject();
|
|
response.AddMember("type", "Success", response.GetAllocator());
|
|
return true;
|
|
}
|
|
|
|
bool Shell::OnServiceProtocolGetDisplayRefreshRate(
|
|
const ServiceProtocol::Handler::ServiceProtocolMap& params,
|
|
rapidjson::Document& response) {
|
|
FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
|
|
response.SetObject();
|
|
response.AddMember("type", "DisplayRefreshRate", response.GetAllocator());
|
|
response.AddMember("fps", engine_->GetDisplayRefreshRate(),
|
|
response.GetAllocator());
|
|
return true;
|
|
}
|
|
|
|
// Service protocol handler
|
|
bool Shell::OnServiceProtocolSetAssetBundlePath(
|
|
const ServiceProtocol::Handler::ServiceProtocolMap& params,
|
|
rapidjson::Document& response) {
|
|
FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
|
|
|
|
if (params.count("assetDirectory") == 0) {
|
|
ServiceProtocolParameterError(response,
|
|
"'assetDirectory' parameter is missing.");
|
|
return false;
|
|
}
|
|
|
|
auto& allocator = response.GetAllocator();
|
|
response.SetObject();
|
|
|
|
auto asset_manager = std::make_shared<AssetManager>();
|
|
|
|
asset_manager->PushFront(std::make_unique<DirectoryAssetBundle>(
|
|
fml::OpenDirectory(params.at("assetDirectory").data(), false,
|
|
fml::FilePermission::kRead)));
|
|
|
|
if (engine_->UpdateAssetManager(std::move(asset_manager))) {
|
|
response.AddMember("type", "Success", allocator);
|
|
auto new_description = GetServiceProtocolDescription();
|
|
rapidjson::Value view(rapidjson::kObjectType);
|
|
new_description.Write(this, view, allocator);
|
|
response.AddMember("view", view, allocator);
|
|
return true;
|
|
} else {
|
|
FML_DLOG(ERROR) << "Could not update asset directory.";
|
|
ServiceProtocolFailureError(response, "Could not update asset directory.");
|
|
return false;
|
|
}
|
|
|
|
FML_DCHECK(false);
|
|
return false;
|
|
}
|
|
|
|
Rasterizer::Screenshot Shell::Screenshot(
|
|
Rasterizer::ScreenshotType screenshot_type,
|
|
bool base64_encode) {
|
|
TRACE_EVENT0("flutter", "Shell::Screenshot");
|
|
fml::AutoResetWaitableEvent latch;
|
|
Rasterizer::Screenshot screenshot;
|
|
fml::TaskRunner::RunNowOrPostTask(
|
|
task_runners_.GetGPUTaskRunner(), [&latch, //
|
|
rasterizer = GetRasterizer(), //
|
|
&screenshot, //
|
|
screenshot_type, //
|
|
base64_encode //
|
|
]() {
|
|
if (rasterizer) {
|
|
screenshot = rasterizer->ScreenshotLastLayerTree(screenshot_type,
|
|
base64_encode);
|
|
}
|
|
latch.Signal();
|
|
});
|
|
latch.Wait();
|
|
return screenshot;
|
|
}
|
|
|
|
fml::Status Shell::WaitForFirstFrame(fml::TimeDelta timeout) {
|
|
FML_DCHECK(is_setup_);
|
|
if (task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread() ||
|
|
task_runners_.GetGPUTaskRunner()->RunsTasksOnCurrentThread()) {
|
|
return fml::Status(fml::StatusCode::kFailedPrecondition,
|
|
"WaitForFirstFrame called from thread that can't wait "
|
|
"because it is responsible for generating the frame.");
|
|
}
|
|
|
|
std::unique_lock<std::mutex> lock(waiting_for_first_frame_mutex_);
|
|
bool success = waiting_for_first_frame_condition_.wait_for(
|
|
lock, std::chrono::milliseconds(timeout.ToMilliseconds()),
|
|
[& waiting_for_first_frame = waiting_for_first_frame_] {
|
|
return !waiting_for_first_frame.load();
|
|
});
|
|
if (success) {
|
|
return fml::Status();
|
|
} else {
|
|
return fml::Status(fml::StatusCode::kDeadlineExceeded, "timeout");
|
|
}
|
|
}
|
|
|
|
bool Shell::ReloadSystemFonts() {
|
|
FML_DCHECK(is_setup_);
|
|
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
|
|
|
|
if (!engine_) {
|
|
return false;
|
|
}
|
|
engine_->GetFontCollection().GetFontCollection()->SetupDefaultFontManager();
|
|
engine_->GetFontCollection().GetFontCollection()->ClearFontFamilyCache();
|
|
// After system fonts are reloaded, we send a system channel message
|
|
// to notify flutter framework.
|
|
rapidjson::Document document;
|
|
document.SetObject();
|
|
auto& allocator = document.GetAllocator();
|
|
rapidjson::Value message_value;
|
|
message_value.SetString(kFontChange, allocator);
|
|
document.AddMember(kTypeKey, message_value, allocator);
|
|
|
|
rapidjson::StringBuffer buffer;
|
|
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
|
document.Accept(writer);
|
|
std::string message = buffer.GetString();
|
|
fml::RefPtr<PlatformMessage> fontsChangeMessage =
|
|
fml::MakeRefCounted<flutter::PlatformMessage>(
|
|
kSystemChannel, std::vector<uint8_t>(message.begin(), message.end()),
|
|
nullptr);
|
|
|
|
OnPlatformViewDispatchPlatformMessage(fontsChangeMessage);
|
|
return true;
|
|
}
|
|
|
|
} // namespace flutter
|