mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Revert "Reland "Remove pipeline in favor of layer tree holder" (#24947)" (flutter/engine#25027)
This commit is contained in:
parent
927937c593
commit
ca7c19d772
@ -643,10 +643,10 @@ FILE: ../../../flutter/shell/common/engine_unittests.cc
|
||||
FILE: ../../../flutter/shell/common/fixtures/shell_test.dart
|
||||
FILE: ../../../flutter/shell/common/fixtures/shelltest_screenshot.png
|
||||
FILE: ../../../flutter/shell/common/input_events_unittests.cc
|
||||
FILE: ../../../flutter/shell/common/layer_tree_holder.cc
|
||||
FILE: ../../../flutter/shell/common/layer_tree_holder.h
|
||||
FILE: ../../../flutter/shell/common/layer_tree_holder_unittests.cc
|
||||
FILE: ../../../flutter/shell/common/persistent_cache_unittests.cc
|
||||
FILE: ../../../flutter/shell/common/pipeline.cc
|
||||
FILE: ../../../flutter/shell/common/pipeline.h
|
||||
FILE: ../../../flutter/shell/common/pipeline_unittests.cc
|
||||
FILE: ../../../flutter/shell/common/platform_view.cc
|
||||
FILE: ../../../flutter/shell/common/platform_view.h
|
||||
FILE: ../../../flutter/shell/common/pointer_data_dispatcher.cc
|
||||
|
||||
@ -69,8 +69,8 @@ source_set("common") {
|
||||
"display_manager.h",
|
||||
"engine.cc",
|
||||
"engine.h",
|
||||
"layer_tree_holder.cc",
|
||||
"layer_tree_holder.h",
|
||||
"pipeline.cc",
|
||||
"pipeline.h",
|
||||
"platform_view.cc",
|
||||
"platform_view.h",
|
||||
"pointer_data_dispatcher.cc",
|
||||
@ -242,8 +242,8 @@ if (enable_unittests) {
|
||||
"canvas_spy_unittests.cc",
|
||||
"engine_unittests.cc",
|
||||
"input_events_unittests.cc",
|
||||
"layer_tree_holder_unittests.cc",
|
||||
"persistent_cache_unittests.cc",
|
||||
"pipeline_unittests.cc",
|
||||
"rasterizer_unittests.cc",
|
||||
"shell_unittests.cc",
|
||||
"skp_shader_warmup_unittests.cc",
|
||||
|
||||
@ -29,7 +29,18 @@ Animator::Animator(Delegate& delegate,
|
||||
last_vsync_start_time_(),
|
||||
last_frame_target_time_(),
|
||||
dart_frame_deadline_(0),
|
||||
layer_tree_holder_(std::make_shared<LayerTreeHolder>()),
|
||||
#if SHELL_ENABLE_METAL
|
||||
layer_tree_pipeline_(fml::MakeRefCounted<LayerTreePipeline>(2)),
|
||||
#else // SHELL_ENABLE_METAL
|
||||
// TODO(dnfield): We should remove this logic and set the pipeline depth
|
||||
// back to 2 in this case. See
|
||||
// https://github.com/flutter/engine/pull/9132 for discussion.
|
||||
layer_tree_pipeline_(fml::MakeRefCounted<LayerTreePipeline>(
|
||||
task_runners.GetPlatformTaskRunner() ==
|
||||
task_runners.GetRasterTaskRunner()
|
||||
? 1
|
||||
: 2)),
|
||||
#endif // SHELL_ENABLE_METAL
|
||||
pending_frame_semaphore_(1),
|
||||
frame_number_(1),
|
||||
paused_(false),
|
||||
@ -37,7 +48,8 @@ Animator::Animator(Delegate& delegate,
|
||||
frame_scheduled_(false),
|
||||
notify_idle_task_id_(0),
|
||||
dimension_change_pending_(false),
|
||||
weak_factory_(this) {}
|
||||
weak_factory_(this) {
|
||||
}
|
||||
|
||||
Animator::~Animator() = default;
|
||||
|
||||
@ -100,6 +112,25 @@ void Animator::BeginFrame(fml::TimePoint vsync_start_time,
|
||||
regenerate_layer_tree_ = false;
|
||||
pending_frame_semaphore_.Signal();
|
||||
|
||||
if (!producer_continuation_) {
|
||||
// We may already have a valid pipeline continuation in case a previous
|
||||
// begin frame did not result in an Animation::Render. Simply reuse that
|
||||
// instead of asking the pipeline for a fresh continuation.
|
||||
producer_continuation_ = layer_tree_pipeline_->Produce();
|
||||
|
||||
if (!producer_continuation_) {
|
||||
// If we still don't have valid continuation, the pipeline is currently
|
||||
// full because the consumer is being too slow. Try again at the next
|
||||
// frame interval.
|
||||
RequestFrame();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// We have acquired a valid continuation from the pipeline and are ready
|
||||
// to service potential frame.
|
||||
FML_DCHECK(producer_continuation_);
|
||||
|
||||
last_frame_begin_time_ = fml::TimePoint::Now();
|
||||
last_vsync_start_time_ = vsync_start_time;
|
||||
fml::tracing::TraceEventAsyncComplete("flutter", "VsyncSchedulingOverhead",
|
||||
@ -153,8 +184,13 @@ void Animator::Render(std::unique_ptr<flutter::LayerTree> layer_tree) {
|
||||
layer_tree->RecordBuildTime(last_vsync_start_time_, last_frame_begin_time_,
|
||||
last_frame_target_time_);
|
||||
|
||||
layer_tree_holder_->PushIfNewer(std::move(layer_tree));
|
||||
delegate_.OnAnimatorDraw(layer_tree_holder_, last_frame_target_time_);
|
||||
// Commit the pending continuation.
|
||||
bool result = producer_continuation_.Complete(std::move(layer_tree));
|
||||
if (!result) {
|
||||
FML_DLOG(INFO) << "No pending continuation to commit";
|
||||
}
|
||||
|
||||
delegate_.OnAnimatorDraw(layer_tree_pipeline_, last_frame_target_time_);
|
||||
}
|
||||
|
||||
bool Animator::CanReuseLastLayerTree() {
|
||||
|
||||
@ -6,14 +6,13 @@
|
||||
#define FLUTTER_SHELL_COMMON_ANIMATOR_H_
|
||||
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
|
||||
#include "flutter/common/task_runners.h"
|
||||
#include "flutter/fml/memory/ref_ptr.h"
|
||||
#include "flutter/fml/memory/weak_ptr.h"
|
||||
#include "flutter/fml/synchronization/semaphore.h"
|
||||
#include "flutter/fml/time/time_point.h"
|
||||
#include "flutter/shell/common/layer_tree_holder.h"
|
||||
#include "flutter/shell/common/pipeline.h"
|
||||
#include "flutter/shell/common/rasterizer.h"
|
||||
#include "flutter/shell/common/vsync_waiter.h"
|
||||
|
||||
@ -36,7 +35,7 @@ class Animator final {
|
||||
virtual void OnAnimatorNotifyIdle(int64_t deadline) = 0;
|
||||
|
||||
virtual void OnAnimatorDraw(
|
||||
std::shared_ptr<LayerTreeHolder> layer_tree_holder,
|
||||
fml::RefPtr<Pipeline<flutter::LayerTree>> pipeline,
|
||||
fml::TimePoint frame_target_time) = 0;
|
||||
|
||||
virtual void OnAnimatorDrawLastLayerTree() = 0;
|
||||
@ -82,6 +81,8 @@ class Animator final {
|
||||
void EnqueueTraceFlowId(uint64_t trace_flow_id);
|
||||
|
||||
private:
|
||||
using LayerTreePipeline = Pipeline<flutter::LayerTree>;
|
||||
|
||||
void BeginFrame(fml::TimePoint frame_start_time,
|
||||
fml::TimePoint frame_target_time);
|
||||
|
||||
@ -103,8 +104,9 @@ class Animator final {
|
||||
fml::TimePoint last_vsync_start_time_;
|
||||
fml::TimePoint last_frame_target_time_;
|
||||
int64_t dart_frame_deadline_;
|
||||
std::shared_ptr<LayerTreeHolder> layer_tree_holder_;
|
||||
fml::RefPtr<LayerTreePipeline> layer_tree_pipeline_;
|
||||
fml::Semaphore pending_frame_semaphore_;
|
||||
LayerTreePipeline::ProducerContinuation producer_continuation_;
|
||||
int64_t frame_number_;
|
||||
bool paused_;
|
||||
bool regenerate_layer_tree_;
|
||||
|
||||
@ -477,18 +477,25 @@ class Engine final : public RuntimeDelegate,
|
||||
/// will cause the jank in the Flutter application:
|
||||
/// * The time taken by this method to create a layer-tree exceeds
|
||||
/// on frame interval (for example, 16.66 ms on a 60Hz display).
|
||||
/// * A new layer-tree produced by this method replaces a stale
|
||||
/// layer tree in `LayerTreeHolder`. See:
|
||||
/// `LayerTreeHolder::ReplaceIfNewer`. This could happen if
|
||||
/// rasterizer takes more than one frame interval to rasterize a
|
||||
/// layer tree. This would cause some frames to be skipped and
|
||||
/// could result in perceptible jank.
|
||||
/// * The time take by this method to generate a new layer-tree
|
||||
/// causes the current layer-tree pipeline depth to change. To
|
||||
/// illustrate this point, note that maximum pipeline depth used
|
||||
/// by layer tree in the engine is 2. If both the UI and GPU
|
||||
/// task runner tasks finish within one frame interval, the
|
||||
/// pipeline depth is one. If the UI thread happens to be
|
||||
/// working on a frame when the raster thread is still not done
|
||||
/// with the previous frame, the pipeline depth is 2. When the
|
||||
/// pipeline depth changes from 1 to 2, animations and UI
|
||||
/// interactions that cause the generation of the new layer tree
|
||||
/// appropriate for (frame_time + one frame interval) will
|
||||
/// actually end up at (frame_time + two frame intervals). This
|
||||
/// is not what code running on the UI thread expected would
|
||||
/// happen. This causes perceptible jank.
|
||||
///
|
||||
/// @param[in] frame_time The point at which the current frame interval
|
||||
/// began. May be used by animation interpolators,
|
||||
/// physics simulations, etc..
|
||||
///
|
||||
/// @see `LayerTreeHolder::ReplaceIfNewer`
|
||||
void BeginFrame(fml::TimePoint frame_time);
|
||||
|
||||
// |HintFreedDelegate|
|
||||
|
||||
@ -1,32 +0,0 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "flutter/shell/common/layer_tree_holder.h"
|
||||
|
||||
namespace flutter {
|
||||
|
||||
LayerTreeHolder::LayerTreeHolder() = default;
|
||||
|
||||
LayerTreeHolder::~LayerTreeHolder() = default;
|
||||
|
||||
std::unique_ptr<LayerTree> LayerTreeHolder::Pop() {
|
||||
std::scoped_lock lock(layer_tree_mutex);
|
||||
return std::move(layer_tree_);
|
||||
}
|
||||
|
||||
void LayerTreeHolder::PushIfNewer(
|
||||
std::unique_ptr<LayerTree> proposed_layer_tree) {
|
||||
std::scoped_lock lock(layer_tree_mutex);
|
||||
if (!layer_tree_ ||
|
||||
layer_tree_->target_time() < proposed_layer_tree->target_time()) {
|
||||
layer_tree_ = std::move(proposed_layer_tree);
|
||||
}
|
||||
}
|
||||
|
||||
bool LayerTreeHolder::IsEmpty() const {
|
||||
std::scoped_lock lock(layer_tree_mutex);
|
||||
return !layer_tree_;
|
||||
}
|
||||
|
||||
}; // namespace flutter
|
||||
@ -1,55 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#ifndef FLUTTER_SHELL_COMMON_LAYER_TREE_HOLDER_H_
|
||||
#define FLUTTER_SHELL_COMMON_LAYER_TREE_HOLDER_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "flow/layers/layer_tree.h"
|
||||
|
||||
namespace flutter {
|
||||
|
||||
/**
|
||||
* @brief Holds the next `flutter::LayerTree` that needs to be rasterized. The
|
||||
* accesses to `LayerTreeHolder` are thread safe. This is important as this
|
||||
* component is accessed from both the UI and the Raster threads.
|
||||
*
|
||||
* A typical flow of events through this component would be:
|
||||
* 1. `flutter::Animator` pushed a layer tree to be rendered during each
|
||||
* `Animator::Render` call.
|
||||
* 2. `flutter::Rasterizer::Draw` consumes the pushed layer tree via `Pop`.
|
||||
*
|
||||
* It is important to note that if a layer tree held by this class is yet to be
|
||||
* consumed, it can be overriden by a newer layer tree produced by the
|
||||
* `Animator`. The newness of the layer tree is determined by the target time.
|
||||
*/
|
||||
class LayerTreeHolder {
|
||||
public:
|
||||
LayerTreeHolder();
|
||||
|
||||
~LayerTreeHolder();
|
||||
|
||||
/**
|
||||
* @brief Checks if a layer tree is currently held.
|
||||
*
|
||||
* @return true is no layer tree is held.
|
||||
* @return false if there is a layer tree waiting to be consumed.
|
||||
*/
|
||||
bool IsEmpty() const;
|
||||
|
||||
[[nodiscard]] std::unique_ptr<LayerTree> Pop();
|
||||
|
||||
void PushIfNewer(std::unique_ptr<LayerTree> proposed_layer_tree);
|
||||
|
||||
private:
|
||||
mutable std::mutex layer_tree_mutex;
|
||||
std::unique_ptr<LayerTree> layer_tree_;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(LayerTreeHolder);
|
||||
};
|
||||
|
||||
}; // namespace flutter
|
||||
|
||||
#endif // FLUTTER_SHELL_COMMON_LAYER_TREE_HOLDER_H_
|
||||
@ -1,76 +0,0 @@
|
||||
// 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 FML_USED_ON_EMBEDDER
|
||||
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
|
||||
#include "flutter/shell/common/layer_tree_holder.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace flutter {
|
||||
namespace testing {
|
||||
|
||||
TEST(LayerTreeHolder, EmptyOnInit) {
|
||||
const LayerTreeHolder layer_tree_holder;
|
||||
ASSERT_TRUE(layer_tree_holder.IsEmpty());
|
||||
}
|
||||
|
||||
TEST(LayerTreeHolder, PutOneAndGet) {
|
||||
LayerTreeHolder layer_tree_holder;
|
||||
const auto frame_size = SkISize::Make(64, 64);
|
||||
auto layer_tree = std::make_unique<LayerTree>(frame_size, 1.0f);
|
||||
layer_tree_holder.PushIfNewer(std::move(layer_tree));
|
||||
ASSERT_FALSE(layer_tree_holder.IsEmpty());
|
||||
const auto stored = layer_tree_holder.Pop();
|
||||
ASSERT_EQ(stored->frame_size(), frame_size);
|
||||
ASSERT_TRUE(layer_tree_holder.IsEmpty());
|
||||
}
|
||||
|
||||
TEST(LayerTreeHolder, PutMultiGetsLatest) {
|
||||
const auto build_begin = fml::TimePoint::Now();
|
||||
const auto target_time_1 = build_begin + fml::TimeDelta::FromSeconds(2);
|
||||
const auto target_time_2 = build_begin + fml::TimeDelta::FromSeconds(5);
|
||||
|
||||
LayerTreeHolder layer_tree_holder;
|
||||
const auto frame_size_1 = SkISize::Make(64, 64);
|
||||
auto layer_tree_1 = std::make_unique<LayerTree>(frame_size_1, 1.0f);
|
||||
layer_tree_1->RecordBuildTime(build_begin, build_begin, target_time_1);
|
||||
layer_tree_holder.PushIfNewer(std::move(layer_tree_1));
|
||||
|
||||
const auto frame_size_2 = SkISize::Make(128, 128);
|
||||
auto layer_tree_2 = std::make_unique<LayerTree>(frame_size_2, 1.0f);
|
||||
layer_tree_2->RecordBuildTime(build_begin, build_begin, target_time_2);
|
||||
layer_tree_holder.PushIfNewer(std::move(layer_tree_2));
|
||||
|
||||
const auto stored = layer_tree_holder.Pop();
|
||||
ASSERT_EQ(stored->frame_size(), frame_size_2);
|
||||
ASSERT_TRUE(layer_tree_holder.IsEmpty());
|
||||
}
|
||||
|
||||
TEST(LayerTreeHolder, RetainsOlderIfNewerFrameHasEarlierTargetTime) {
|
||||
const auto build_begin = fml::TimePoint::Now();
|
||||
const auto target_time_1 = build_begin + fml::TimeDelta::FromSeconds(5);
|
||||
const auto target_time_2 = build_begin + fml::TimeDelta::FromSeconds(2);
|
||||
|
||||
LayerTreeHolder layer_tree_holder;
|
||||
const auto frame_size_1 = SkISize::Make(64, 64);
|
||||
auto layer_tree_1 = std::make_unique<LayerTree>(frame_size_1, 1.0f);
|
||||
layer_tree_1->RecordBuildTime(build_begin, build_begin, target_time_1);
|
||||
layer_tree_holder.PushIfNewer(std::move(layer_tree_1));
|
||||
|
||||
const auto frame_size_2 = SkISize::Make(128, 128);
|
||||
auto layer_tree_2 = std::make_unique<LayerTree>(frame_size_2, 1.0f);
|
||||
layer_tree_2->RecordBuildTime(build_begin, build_begin, target_time_2);
|
||||
layer_tree_holder.PushIfNewer(std::move(layer_tree_2));
|
||||
|
||||
const auto stored = layer_tree_holder.Pop();
|
||||
ASSERT_EQ(stored->frame_size(), frame_size_1);
|
||||
ASSERT_TRUE(layer_tree_holder.IsEmpty());
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace flutter
|
||||
14
engine/src/flutter/shell/common/pipeline.cc
Normal file
14
engine/src/flutter/shell/common/pipeline.cc
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "flutter/shell/common/pipeline.h"
|
||||
|
||||
namespace flutter {
|
||||
|
||||
size_t GetNextPipelineTraceID() {
|
||||
static std::atomic_size_t PipelineLastTraceID = {0};
|
||||
return ++PipelineLastTraceID;
|
||||
}
|
||||
|
||||
} // namespace flutter
|
||||
215
engine/src/flutter/shell/common/pipeline.h
Normal file
215
engine/src/flutter/shell/common/pipeline.h
Normal file
@ -0,0 +1,215 @@
|
||||
// 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.
|
||||
|
||||
#ifndef FLUTTER_SHELL_COMMON_PIPELINE_H_
|
||||
#define FLUTTER_SHELL_COMMON_PIPELINE_H_
|
||||
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#include "flutter/fml/macros.h"
|
||||
#include "flutter/fml/memory/ref_counted.h"
|
||||
#include "flutter/fml/synchronization/semaphore.h"
|
||||
#include "flutter/fml/trace_event.h"
|
||||
|
||||
namespace flutter {
|
||||
|
||||
enum class PipelineConsumeResult {
|
||||
NoneAvailable,
|
||||
Done,
|
||||
MoreAvailable,
|
||||
};
|
||||
|
||||
size_t GetNextPipelineTraceID();
|
||||
|
||||
/// A thread-safe queue of resources for a single consumer and a single
|
||||
/// producer.
|
||||
template <class R>
|
||||
class Pipeline : public fml::RefCountedThreadSafe<Pipeline<R>> {
|
||||
public:
|
||||
using Resource = R;
|
||||
using ResourcePtr = std::unique_ptr<Resource>;
|
||||
|
||||
/// Denotes a spot in the pipeline reserved for the producer to finish
|
||||
/// preparing a completed pipeline resource.
|
||||
class ProducerContinuation {
|
||||
public:
|
||||
ProducerContinuation() : trace_id_(0) {}
|
||||
|
||||
ProducerContinuation(ProducerContinuation&& other)
|
||||
: continuation_(other.continuation_), trace_id_(other.trace_id_) {
|
||||
other.continuation_ = nullptr;
|
||||
other.trace_id_ = 0;
|
||||
}
|
||||
|
||||
ProducerContinuation& operator=(ProducerContinuation&& other) {
|
||||
std::swap(continuation_, other.continuation_);
|
||||
std::swap(trace_id_, other.trace_id_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
~ProducerContinuation() {
|
||||
if (continuation_) {
|
||||
continuation_(nullptr, trace_id_);
|
||||
TRACE_EVENT_ASYNC_END0("flutter", "PipelineProduce", trace_id_);
|
||||
// The continuation is being dropped on the floor. End the flow.
|
||||
TRACE_FLOW_END("flutter", "PipelineItem", trace_id_);
|
||||
TRACE_EVENT_ASYNC_END0("flutter", "PipelineItem", trace_id_);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool Complete(ResourcePtr resource) {
|
||||
bool result = false;
|
||||
if (continuation_) {
|
||||
result = continuation_(std::move(resource), trace_id_);
|
||||
continuation_ = nullptr;
|
||||
TRACE_EVENT_ASYNC_END0("flutter", "PipelineProduce", trace_id_);
|
||||
TRACE_FLOW_STEP("flutter", "PipelineItem", trace_id_);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
operator bool() const { return continuation_ != nullptr; }
|
||||
|
||||
private:
|
||||
friend class Pipeline;
|
||||
using Continuation = std::function<bool(ResourcePtr, size_t)>;
|
||||
|
||||
Continuation continuation_;
|
||||
size_t trace_id_;
|
||||
|
||||
ProducerContinuation(const Continuation& continuation, size_t trace_id)
|
||||
: continuation_(continuation), trace_id_(trace_id) {
|
||||
TRACE_FLOW_BEGIN("flutter", "PipelineItem", trace_id_);
|
||||
TRACE_EVENT_ASYNC_BEGIN0("flutter", "PipelineItem", trace_id_);
|
||||
TRACE_EVENT_ASYNC_BEGIN0("flutter", "PipelineProduce", trace_id_);
|
||||
}
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(ProducerContinuation);
|
||||
};
|
||||
|
||||
explicit Pipeline(uint32_t depth)
|
||||
: depth_(depth), empty_(depth), available_(0), inflight_(0) {}
|
||||
|
||||
~Pipeline() = default;
|
||||
|
||||
bool IsValid() const { return empty_.IsValid() && available_.IsValid(); }
|
||||
|
||||
ProducerContinuation Produce() {
|
||||
if (!empty_.TryWait()) {
|
||||
return {};
|
||||
}
|
||||
++inflight_;
|
||||
FML_TRACE_COUNTER("flutter", "Pipeline Depth",
|
||||
reinterpret_cast<int64_t>(this), //
|
||||
"frames in flight", inflight_.load() //
|
||||
);
|
||||
|
||||
return ProducerContinuation{
|
||||
std::bind(&Pipeline::ProducerCommit, this, std::placeholders::_1,
|
||||
std::placeholders::_2), // continuation
|
||||
GetNextPipelineTraceID()}; // trace id
|
||||
}
|
||||
|
||||
// Create a `ProducerContinuation` that will only push the task if the queue
|
||||
// is empty.
|
||||
// Prefer using |Produce|. ProducerContinuation returned by this method
|
||||
// doesn't guarantee that the frame will be rendered.
|
||||
ProducerContinuation ProduceIfEmpty() {
|
||||
if (!empty_.TryWait()) {
|
||||
return {};
|
||||
}
|
||||
++inflight_;
|
||||
FML_TRACE_COUNTER("flutter", "Pipeline Depth",
|
||||
reinterpret_cast<int64_t>(this), //
|
||||
"frames in flight", inflight_.load() //
|
||||
);
|
||||
|
||||
return ProducerContinuation{
|
||||
std::bind(&Pipeline::ProducerCommitIfEmpty, this, std::placeholders::_1,
|
||||
std::placeholders::_2), // continuation
|
||||
GetNextPipelineTraceID()}; // trace id
|
||||
}
|
||||
|
||||
using Consumer = std::function<void(ResourcePtr)>;
|
||||
|
||||
/// @note Procedure doesn't copy all closures.
|
||||
[[nodiscard]] PipelineConsumeResult Consume(const Consumer& consumer) {
|
||||
if (consumer == nullptr) {
|
||||
return PipelineConsumeResult::NoneAvailable;
|
||||
}
|
||||
|
||||
if (!available_.TryWait()) {
|
||||
return PipelineConsumeResult::NoneAvailable;
|
||||
}
|
||||
|
||||
ResourcePtr resource;
|
||||
size_t trace_id = 0;
|
||||
size_t items_count = 0;
|
||||
|
||||
{
|
||||
std::scoped_lock lock(queue_mutex_);
|
||||
std::tie(resource, trace_id) = std::move(queue_.front());
|
||||
queue_.pop_front();
|
||||
items_count = queue_.size();
|
||||
}
|
||||
|
||||
{
|
||||
TRACE_EVENT0("flutter", "PipelineConsume");
|
||||
consumer(std::move(resource));
|
||||
}
|
||||
|
||||
empty_.Signal();
|
||||
--inflight_;
|
||||
|
||||
TRACE_FLOW_END("flutter", "PipelineItem", trace_id);
|
||||
TRACE_EVENT_ASYNC_END0("flutter", "PipelineItem", trace_id);
|
||||
|
||||
return items_count > 0 ? PipelineConsumeResult::MoreAvailable
|
||||
: PipelineConsumeResult::Done;
|
||||
}
|
||||
|
||||
private:
|
||||
const uint32_t depth_;
|
||||
fml::Semaphore empty_;
|
||||
fml::Semaphore available_;
|
||||
std::atomic<int> inflight_;
|
||||
std::mutex queue_mutex_;
|
||||
std::deque<std::pair<ResourcePtr, size_t>> queue_;
|
||||
|
||||
bool ProducerCommit(ResourcePtr resource, size_t trace_id) {
|
||||
{
|
||||
std::scoped_lock lock(queue_mutex_);
|
||||
queue_.emplace_back(std::move(resource), trace_id);
|
||||
}
|
||||
|
||||
// Ensure the queue mutex is not held as that would be a pessimization.
|
||||
available_.Signal();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProducerCommitIfEmpty(ResourcePtr resource, size_t trace_id) {
|
||||
{
|
||||
std::scoped_lock lock(queue_mutex_);
|
||||
if (!queue_.empty()) {
|
||||
// Bail if the queue is not empty, opens up spaces to produce other
|
||||
// frames.
|
||||
empty_.Signal();
|
||||
return false;
|
||||
}
|
||||
queue_.emplace_back(std::move(resource), trace_id);
|
||||
}
|
||||
|
||||
// Ensure the queue mutex is not held as that would be a pessimization.
|
||||
available_.Signal();
|
||||
return true;
|
||||
}
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(Pipeline);
|
||||
};
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
#endif // FLUTTER_SHELL_COMMON_PIPELINE_H_
|
||||
135
engine/src/flutter/shell/common/pipeline_unittests.cc
Normal file
135
engine/src/flutter/shell/common/pipeline_unittests.cc
Normal file
@ -0,0 +1,135 @@
|
||||
// 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 FML_USED_ON_EMBEDDER
|
||||
|
||||
#include "flutter/shell/common/pipeline.h"
|
||||
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace flutter {
|
||||
namespace testing {
|
||||
|
||||
using IntPipeline = Pipeline<int>;
|
||||
using Continuation = IntPipeline::ProducerContinuation;
|
||||
|
||||
TEST(PipelineTest, ConsumeOneVal) {
|
||||
fml::RefPtr<IntPipeline> pipeline = fml::MakeRefCounted<IntPipeline>(2);
|
||||
|
||||
Continuation continuation = pipeline->Produce();
|
||||
|
||||
const int test_val = 1;
|
||||
bool result = continuation.Complete(std::make_unique<int>(test_val));
|
||||
ASSERT_EQ(result, true);
|
||||
|
||||
PipelineConsumeResult consume_result = pipeline->Consume(
|
||||
[&test_val](std::unique_ptr<int> v) { ASSERT_EQ(*v, test_val); });
|
||||
|
||||
ASSERT_EQ(consume_result, PipelineConsumeResult::Done);
|
||||
}
|
||||
|
||||
TEST(PipelineTest, ContinuationCanOnlyBeUsedOnce) {
|
||||
fml::RefPtr<IntPipeline> pipeline = fml::MakeRefCounted<IntPipeline>(2);
|
||||
|
||||
Continuation continuation = pipeline->Produce();
|
||||
|
||||
const int test_val = 1;
|
||||
bool result = continuation.Complete(std::make_unique<int>(test_val));
|
||||
ASSERT_EQ(result, true);
|
||||
|
||||
PipelineConsumeResult consume_result_1 = pipeline->Consume(
|
||||
[&test_val](std::unique_ptr<int> v) { ASSERT_EQ(*v, test_val); });
|
||||
|
||||
result = continuation.Complete(std::make_unique<int>(test_val));
|
||||
ASSERT_EQ(result, false);
|
||||
ASSERT_EQ(consume_result_1, PipelineConsumeResult::Done);
|
||||
|
||||
PipelineConsumeResult consume_result_2 =
|
||||
pipeline->Consume([](std::unique_ptr<int> v) { FAIL(); });
|
||||
|
||||
result = continuation.Complete(std::make_unique<int>(test_val));
|
||||
ASSERT_EQ(result, false);
|
||||
ASSERT_EQ(consume_result_2, PipelineConsumeResult::NoneAvailable);
|
||||
}
|
||||
|
||||
TEST(PipelineTest, PushingMoreThanDepthCompletesFirstSubmission) {
|
||||
const int depth = 1;
|
||||
fml::RefPtr<IntPipeline> pipeline = fml::MakeRefCounted<IntPipeline>(depth);
|
||||
|
||||
Continuation continuation_1 = pipeline->Produce();
|
||||
Continuation continuation_2 = pipeline->Produce();
|
||||
|
||||
const int test_val_1 = 1, test_val_2 = 2;
|
||||
bool result = continuation_1.Complete(std::make_unique<int>(test_val_1));
|
||||
ASSERT_EQ(result, true);
|
||||
result = continuation_2.Complete(std::make_unique<int>(test_val_2));
|
||||
ASSERT_EQ(result, false);
|
||||
|
||||
PipelineConsumeResult consume_result_1 = pipeline->Consume(
|
||||
[&test_val_1](std::unique_ptr<int> v) { ASSERT_EQ(*v, test_val_1); });
|
||||
|
||||
ASSERT_EQ(consume_result_1, PipelineConsumeResult::Done);
|
||||
}
|
||||
|
||||
TEST(PipelineTest, PushingMultiProcessesInOrder) {
|
||||
const int depth = 2;
|
||||
fml::RefPtr<IntPipeline> pipeline = fml::MakeRefCounted<IntPipeline>(depth);
|
||||
|
||||
Continuation continuation_1 = pipeline->Produce();
|
||||
Continuation continuation_2 = pipeline->Produce();
|
||||
|
||||
const int test_val_1 = 1, test_val_2 = 2;
|
||||
bool result = continuation_1.Complete(std::make_unique<int>(test_val_1));
|
||||
ASSERT_EQ(result, true);
|
||||
result = continuation_2.Complete(std::make_unique<int>(test_val_2));
|
||||
ASSERT_EQ(result, true);
|
||||
|
||||
PipelineConsumeResult consume_result_1 = pipeline->Consume(
|
||||
[&test_val_1](std::unique_ptr<int> v) { ASSERT_EQ(*v, test_val_1); });
|
||||
ASSERT_EQ(consume_result_1, PipelineConsumeResult::MoreAvailable);
|
||||
|
||||
PipelineConsumeResult consume_result_2 = pipeline->Consume(
|
||||
[&test_val_2](std::unique_ptr<int> v) { ASSERT_EQ(*v, test_val_2); });
|
||||
ASSERT_EQ(consume_result_2, PipelineConsumeResult::Done);
|
||||
}
|
||||
|
||||
TEST(PipelineTest, ProduceIfEmptyDoesNotConsumeWhenQueueIsNotEmpty) {
|
||||
const int depth = 2;
|
||||
fml::RefPtr<IntPipeline> pipeline = fml::MakeRefCounted<IntPipeline>(depth);
|
||||
|
||||
Continuation continuation_1 = pipeline->Produce();
|
||||
Continuation continuation_2 = pipeline->ProduceIfEmpty();
|
||||
|
||||
const int test_val_1 = 1, test_val_2 = 2;
|
||||
bool result = continuation_1.Complete(std::make_unique<int>(test_val_1));
|
||||
ASSERT_EQ(result, true);
|
||||
result = continuation_2.Complete(std::make_unique<int>(test_val_2));
|
||||
ASSERT_EQ(result, false);
|
||||
|
||||
PipelineConsumeResult consume_result_1 = pipeline->Consume(
|
||||
[&test_val_1](std::unique_ptr<int> v) { ASSERT_EQ(*v, test_val_1); });
|
||||
ASSERT_EQ(consume_result_1, PipelineConsumeResult::Done);
|
||||
}
|
||||
|
||||
TEST(PipelineTest, ProduceIfEmptySuccessfulIfQueueIsEmpty) {
|
||||
const int depth = 1;
|
||||
fml::RefPtr<IntPipeline> pipeline = fml::MakeRefCounted<IntPipeline>(depth);
|
||||
|
||||
Continuation continuation_1 = pipeline->ProduceIfEmpty();
|
||||
|
||||
const int test_val_1 = 1;
|
||||
bool result = continuation_1.Complete(std::make_unique<int>(test_val_1));
|
||||
ASSERT_EQ(result, true);
|
||||
|
||||
PipelineConsumeResult consume_result_1 = pipeline->Consume(
|
||||
[&test_val_1](std::unique_ptr<int> v) { ASSERT_EQ(*v, test_val_1); });
|
||||
ASSERT_EQ(consume_result_1, PipelineConsumeResult::Done);
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace flutter
|
||||
@ -151,12 +151,8 @@ void Rasterizer::DrawLastLayerTree() {
|
||||
DrawToSurface(*last_layer_tree_);
|
||||
}
|
||||
|
||||
void Rasterizer::Draw(std::shared_ptr<LayerTreeHolder> layer_tree_holder,
|
||||
void Rasterizer::Draw(fml::RefPtr<Pipeline<flutter::LayerTree>> pipeline,
|
||||
LayerTreeDiscardCallback discardCallback) {
|
||||
if (layer_tree_holder->IsEmpty()) {
|
||||
// We do not have any frame to raster.
|
||||
return;
|
||||
}
|
||||
TRACE_EVENT0("flutter", "GPURasterizer::Draw");
|
||||
if (raster_thread_merger_ &&
|
||||
!raster_thread_merger_->IsOnRasterizingThread()) {
|
||||
@ -167,40 +163,54 @@ void Rasterizer::Draw(std::shared_ptr<LayerTreeHolder> layer_tree_holder,
|
||||
.GetRasterTaskRunner()
|
||||
->RunsTasksOnCurrentThread());
|
||||
|
||||
std::unique_ptr<LayerTree> layer_tree = layer_tree_holder->Pop();
|
||||
RasterStatus raster_status = RasterStatus::kFailed;
|
||||
Pipeline<flutter::LayerTree>::Consumer consumer =
|
||||
[&](std::unique_ptr<LayerTree> layer_tree) {
|
||||
if (discardCallback(*layer_tree.get())) {
|
||||
raster_status = RasterStatus::kDiscarded;
|
||||
} else {
|
||||
raster_status = DoDraw(std::move(layer_tree));
|
||||
}
|
||||
};
|
||||
|
||||
RasterStatus raster_status;
|
||||
if (layer_tree) {
|
||||
if (discardCallback(*layer_tree.get())) {
|
||||
raster_status = RasterStatus::kDiscarded;
|
||||
} else {
|
||||
raster_status = DoDraw(std::move(layer_tree));
|
||||
}
|
||||
} else {
|
||||
raster_status = RasterStatus::kFailed;
|
||||
}
|
||||
PipelineConsumeResult consume_result = pipeline->Consume(consumer);
|
||||
// if the raster status is to resubmit the frame, we push the frame to the
|
||||
// front of the queue and also change the consume status to more available.
|
||||
|
||||
// Merging the thread as we know the next `Draw` should be run on the
|
||||
// platform thread.
|
||||
auto should_resubmit_frame = raster_status == RasterStatus::kResubmit ||
|
||||
raster_status == RasterStatus::kSkipAndRetry;
|
||||
if (should_resubmit_frame) {
|
||||
layer_tree_holder->PushIfNewer(std::move(resubmitted_layer_tree_));
|
||||
FML_DCHECK(external_view_embedder_ != nullptr)
|
||||
<< "kResubmit is an invalid raster status without external view "
|
||||
"embedder.";
|
||||
delegate_.GetTaskRunners().GetRasterTaskRunner()->PostTask(
|
||||
[weak_this = weak_factory_.GetWeakPtr(), layer_tree_holder]() {
|
||||
if (weak_this) {
|
||||
weak_this->Draw(layer_tree_holder);
|
||||
}
|
||||
});
|
||||
auto front_continuation = pipeline->ProduceIfEmpty();
|
||||
bool result =
|
||||
front_continuation.Complete(std::move(resubmitted_layer_tree_));
|
||||
if (result) {
|
||||
consume_result = PipelineConsumeResult::MoreAvailable;
|
||||
}
|
||||
} else if (raster_status == RasterStatus::kEnqueuePipeline) {
|
||||
consume_result = PipelineConsumeResult::MoreAvailable;
|
||||
}
|
||||
|
||||
// EndFrame should perform cleanups for the external_view_embedder.
|
||||
if (surface_ && external_view_embedder_) {
|
||||
external_view_embedder_->EndFrame(should_resubmit_frame,
|
||||
raster_thread_merger_);
|
||||
}
|
||||
|
||||
// Consume as many pipeline items as possible. But yield the event loop
|
||||
// between successive tries.
|
||||
switch (consume_result) {
|
||||
case PipelineConsumeResult::MoreAvailable: {
|
||||
delegate_.GetTaskRunners().GetRasterTaskRunner()->PostTask(
|
||||
[weak_this = weak_factory_.GetWeakPtr(), pipeline]() {
|
||||
if (weak_this) {
|
||||
weak_this->Draw(pipeline);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
@ -285,9 +295,9 @@ sk_sp<SkImage> Rasterizer::MakeRasterSnapshot(sk_sp<SkPicture> picture,
|
||||
sk_sp<SkImage> Rasterizer::ConvertToRasterImage(sk_sp<SkImage> image) {
|
||||
TRACE_EVENT0("flutter", __FUNCTION__);
|
||||
|
||||
// If the rasterizer does not have a surface with a GrContext, then it
|
||||
// will be unable to render a cross-context SkImage. The caller will need
|
||||
// to create the raster image on the IO thread.
|
||||
// If the rasterizer does not have a surface with a GrContext, then it will
|
||||
// be unable to render a cross-context SkImage. The caller will need to
|
||||
// create the raster image on the IO thread.
|
||||
if (surface_ == nullptr || surface_->GetContext() == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
@ -342,8 +352,8 @@ RasterStatus Rasterizer::DoDraw(
|
||||
}
|
||||
|
||||
// TODO(liyuqian): in Fuchsia, the rasterization doesn't finish when
|
||||
// Rasterizer::DoDraw finishes. Future work is needed to adapt the
|
||||
// timestamp for Fuchsia to capture SceneUpdateContext::ExecutePaintTasks.
|
||||
// Rasterizer::DoDraw finishes. Future work is needed to adapt the timestamp
|
||||
// for Fuchsia to capture SceneUpdateContext::ExecutePaintTasks.
|
||||
const auto raster_finish_time = fml::TimePoint::Now();
|
||||
timing.Set(FrameTiming::kRasterFinish, raster_finish_time);
|
||||
delegate_.OnFrameRasterized(timing);
|
||||
@ -421,20 +431,18 @@ RasterStatus Rasterizer::DrawToSurface(flutter::LayerTree& layer_tree) {
|
||||
embedder_root_canvas = external_view_embedder_->GetRootCanvas();
|
||||
}
|
||||
|
||||
// On Android, the external view embedder deletes surfaces in
|
||||
// `BeginFrame`.
|
||||
// On Android, the external view embedder deletes surfaces in `BeginFrame`.
|
||||
//
|
||||
// Deleting a surface also clears the GL context. Therefore, acquire the
|
||||
// frame after calling `BeginFrame` as this operation resets the GL
|
||||
// context.
|
||||
// frame after calling `BeginFrame` as this operation resets the GL context.
|
||||
auto frame = surface_->AcquireFrame(layer_tree.frame_size());
|
||||
|
||||
if (frame == nullptr) {
|
||||
return RasterStatus::kFailed;
|
||||
}
|
||||
|
||||
// If the external view embedder has specified an optional root surface,
|
||||
// the root surface transformation is set by the embedder instead of
|
||||
// If the external view embedder has specified an optional root surface, the
|
||||
// root surface transformation is set by the embedder instead of
|
||||
// having to apply it here.
|
||||
SkMatrix root_surface_transformation =
|
||||
embedder_root_canvas ? SkMatrix{} : surface_->GetRootTransformation();
|
||||
@ -462,8 +470,7 @@ RasterStatus Rasterizer::DrawToSurface(flutter::LayerTree& layer_tree) {
|
||||
raster_thread_merger_->IsMerged()) {
|
||||
// TODO(73620): Remove when platform views are accounted for.
|
||||
FML_LOG(ERROR)
|
||||
<< "Error: Thread merging not implemented for engines with "
|
||||
"shared "
|
||||
<< "Error: Thread merging not implemented for engines with shared "
|
||||
"components.\n\n"
|
||||
"This is likely a result of using platform views with enigne "
|
||||
"groups. See "
|
||||
@ -528,8 +535,8 @@ static sk_sp<SkSurface> CreateSnapshotSurface(GrDirectContext* surface_context,
|
||||
const auto image_info = SkImageInfo::MakeN32Premul(
|
||||
size.width(), size.height(), SkColorSpace::MakeSRGB());
|
||||
if (surface_context) {
|
||||
// There is a rendering surface that may contain textures that are going
|
||||
// to be referenced in the layer tree about to be drawn.
|
||||
// There is a rendering surface that may contain textures that are going to
|
||||
// be referenced in the layer tree about to be drawn.
|
||||
return SkSurface::MakeRenderTarget(surface_context, //
|
||||
SkBudgeted::kNo, //
|
||||
image_info //
|
||||
@ -546,8 +553,8 @@ sk_sp<SkData> Rasterizer::ScreenshotLayerTreeAsImage(
|
||||
flutter::CompositorContext& compositor_context,
|
||||
GrDirectContext* surface_context,
|
||||
bool compressed) {
|
||||
// Attempt to create a snapshot surface depending on whether we have
|
||||
// access to a valid GPU rendering context.
|
||||
// Attempt to create a snapshot surface depending on whether we have access to
|
||||
// a valid GPU rendering context.
|
||||
auto snapshot_surface =
|
||||
CreateSnapshotSurface(surface_context, tree->frame_size());
|
||||
if (snapshot_surface == nullptr) {
|
||||
@ -558,15 +565,15 @@ sk_sp<SkData> Rasterizer::ScreenshotLayerTreeAsImage(
|
||||
// Draw the current layer tree into the snapshot surface.
|
||||
auto* canvas = snapshot_surface->getCanvas();
|
||||
|
||||
// There is no root surface transformation for the screenshot layer. Reset
|
||||
// the matrix to identity.
|
||||
// There is no root surface transformation for the screenshot layer. Reset the
|
||||
// matrix to identity.
|
||||
SkMatrix root_surface_transformation;
|
||||
root_surface_transformation.reset();
|
||||
|
||||
// snapshot_surface->makeImageSnapshot needs the GL context to be set if
|
||||
// the render context is GL. frame->Raster() pops the gl context in
|
||||
// platforms that gl context switching are used. (For example, older iOS
|
||||
// that uses GL) We reset the GL context using the context switch.
|
||||
// snapshot_surface->makeImageSnapshot needs the GL context to be set if the
|
||||
// render context is GL. frame->Raster() pops the gl context in platforms that
|
||||
// gl context switching are used. (For example, older iOS that uses GL) We
|
||||
// reset the GL context using the context switch.
|
||||
auto context_switch = surface_->MakeRenderContextCurrent();
|
||||
if (!context_switch->GetResult()) {
|
||||
FML_LOG(ERROR) << "Screenshot: unable to make image screenshot";
|
||||
@ -580,8 +587,7 @@ sk_sp<SkData> Rasterizer::ScreenshotLayerTreeAsImage(
|
||||
frame->Raster(*tree, true);
|
||||
canvas->flush();
|
||||
|
||||
// Prepare an image from the surface, this image may potentially be on th
|
||||
// GPU.
|
||||
// Prepare an image from the surface, this image may potentially be on th GPU.
|
||||
auto potentially_gpu_snapshot = snapshot_surface->makeImageSnapshot();
|
||||
if (!potentially_gpu_snapshot) {
|
||||
FML_LOG(ERROR) << "Screenshot: unable to make image screenshot";
|
||||
@ -595,8 +601,8 @@ sk_sp<SkData> Rasterizer::ScreenshotLayerTreeAsImage(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// If the caller want the pixels to be compressed, there is a Skia utility
|
||||
// to compress to PNG. Use that.
|
||||
// If the caller want the pixels to be compressed, there is a Skia utility to
|
||||
// compress to PNG. Use that.
|
||||
if (compressed) {
|
||||
return cpu_snapshot->encodeToData();
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
#include "flutter/fml/time/time_delta.h"
|
||||
#include "flutter/fml/time/time_point.h"
|
||||
#include "flutter/lib/ui/snapshot_delegate.h"
|
||||
#include "flutter/shell/common/layer_tree_holder.h"
|
||||
#include "flutter/shell/common/pipeline.h"
|
||||
|
||||
namespace flutter {
|
||||
|
||||
@ -211,30 +211,37 @@ class Rasterizer final : public SnapshotDelegate {
|
||||
using LayerTreeDiscardCallback = std::function<bool(flutter::LayerTree&)>;
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Takes the latest item from the layer tree holder and executes
|
||||
/// the raster thread frame workload for that item to render a
|
||||
/// frame on the on-screen surface.
|
||||
/// @brief Takes the next item from the layer tree pipeline and executes
|
||||
/// the raster thread frame workload for that pipeline item to
|
||||
/// render a frame on the on-screen surface.
|
||||
///
|
||||
/// Why does the draw call take a layer tree holder and not the
|
||||
/// Why does the draw call take a layer tree pipeline and not the
|
||||
/// layer tree directly?
|
||||
///
|
||||
/// The layer tree holder is a thread safe way to produce frame
|
||||
/// workloads from the UI thread and rasterize them on the raster
|
||||
/// thread. To account for scenarious where the UI thread
|
||||
/// continues to produce the frames while a raster task is queued,
|
||||
/// `Rasterizer::DoDraw` that gets executed on the raster thread
|
||||
/// must pick up the newest layer tree produced by the UI thread.
|
||||
/// If we were to pass the layer tree as opposed to the holder, it
|
||||
/// would result in stale frames being rendered.
|
||||
/// The pipeline is the way book-keeping of frame workloads
|
||||
/// distributed across the multiple threads is managed. The
|
||||
/// rasterizer deals with the pipelines directly (instead of layer
|
||||
/// trees which is what it actually renders) because the pipeline
|
||||
/// consumer's workload must be accounted for within the pipeline
|
||||
/// itself. If the rasterizer took the layer tree directly, it
|
||||
/// would have to be taken out of the pipeline. That would signal
|
||||
/// the end of the frame workload and the pipeline would be ready
|
||||
/// for new frames. But the last frame has not been rendered by
|
||||
/// the frame yet! On the other hand, the pipeline must own the
|
||||
/// layer tree it renders because it keeps a reference to the last
|
||||
/// layer tree around till a new frame is rendered. So a simple
|
||||
/// reference wont work either. The `Rasterizer::DoDraw` method
|
||||
/// actually performs the GPU operations within the layer tree
|
||||
/// pipeline.
|
||||
///
|
||||
/// @see `Rasterizer::DoDraw`
|
||||
///
|
||||
/// @param[in] layer_tree_holder The layer tree holder to take the latest
|
||||
/// layer tree to render from.
|
||||
/// @param[in] pipeline The layer tree pipeline to take the next layer tree
|
||||
/// to render from.
|
||||
/// @param[in] discardCallback if specified and returns true, the layer tree
|
||||
/// is discarded instead of being rendered
|
||||
///
|
||||
void Draw(std::shared_ptr<LayerTreeHolder> layer_tree_holder,
|
||||
void Draw(fml::RefPtr<Pipeline<flutter::LayerTree>> pipeline,
|
||||
LayerTreeDiscardCallback discardCallback = NoDiscard);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@ -444,8 +451,7 @@ class Rasterizer final : public SnapshotDelegate {
|
||||
std::unique_ptr<flutter::LayerTree> last_layer_tree_;
|
||||
// Set when we need attempt to rasterize the layer tree again. This layer_tree
|
||||
// has not successfully rasterized. This can happen due to the change in the
|
||||
// thread configuration. This layer tree could be rasterized again if there
|
||||
// are no newer ones.
|
||||
// thread configuration. This will be inserted to the front of the pipeline.
|
||||
std::unique_ptr<flutter::LayerTree> resubmitted_layer_tree_;
|
||||
fml::closure next_frame_callback_;
|
||||
bool user_override_resource_cache_bytes_;
|
||||
|
||||
@ -6,7 +6,6 @@
|
||||
|
||||
#include "flutter/shell/common/rasterizer.h"
|
||||
|
||||
#include "flutter/shell/common/layer_tree_holder.h"
|
||||
#include "flutter/shell/common/thread_host.h"
|
||||
#include "flutter/testing/testing.h"
|
||||
#include "gmock/gmock.h"
|
||||
@ -91,7 +90,8 @@ TEST(RasterizerTest, drawEmptyPipeline) {
|
||||
rasterizer->Setup(std::move(surface));
|
||||
fml::AutoResetWaitableEvent latch;
|
||||
thread_host.raster_thread->GetTaskRunner()->PostTask([&] {
|
||||
rasterizer->Draw(std::make_unique<LayerTreeHolder>(), nullptr);
|
||||
auto pipeline = fml::AdoptRef(new Pipeline<LayerTree>(/*depth=*/10));
|
||||
rasterizer->Draw(pipeline, nullptr);
|
||||
latch.Signal();
|
||||
});
|
||||
latch.Wait();
|
||||
@ -142,12 +142,13 @@ TEST(RasterizerTest,
|
||||
rasterizer->Setup(std::move(surface));
|
||||
fml::AutoResetWaitableEvent latch;
|
||||
thread_host.raster_thread->GetTaskRunner()->PostTask([&] {
|
||||
auto pipeline = fml::AdoptRef(new Pipeline<LayerTree>(/*depth=*/10));
|
||||
auto layer_tree = std::make_unique<LayerTree>(/*frame_size=*/SkISize(),
|
||||
/*device_pixel_ratio=*/2.0f);
|
||||
auto layer_tree_holder = std::make_unique<LayerTreeHolder>();
|
||||
layer_tree_holder->PushIfNewer(std::move(layer_tree));
|
||||
bool result = pipeline->Produce().Complete(std::move(layer_tree));
|
||||
EXPECT_TRUE(result);
|
||||
auto no_discard = [](LayerTree&) { return false; };
|
||||
rasterizer->Draw(std::move(layer_tree_holder), no_discard);
|
||||
rasterizer->Draw(pipeline, no_discard);
|
||||
latch.Signal();
|
||||
});
|
||||
latch.Wait();
|
||||
@ -195,12 +196,13 @@ TEST(
|
||||
rasterizer->Setup(std::move(surface));
|
||||
fml::AutoResetWaitableEvent latch;
|
||||
thread_host.raster_thread->GetTaskRunner()->PostTask([&] {
|
||||
auto pipeline = fml::AdoptRef(new Pipeline<LayerTree>(/*depth=*/10));
|
||||
auto layer_tree = std::make_unique<LayerTree>(/*frame_size=*/SkISize(),
|
||||
/*device_pixel_ratio=*/2.0f);
|
||||
auto layer_tree_holder = std::make_unique<LayerTreeHolder>();
|
||||
layer_tree_holder->PushIfNewer(std::move(std::move(layer_tree)));
|
||||
bool result = pipeline->Produce().Complete(std::move(layer_tree));
|
||||
EXPECT_TRUE(result);
|
||||
auto no_discard = [](LayerTree&) { return false; };
|
||||
rasterizer->Draw(std::move(layer_tree_holder), no_discard);
|
||||
rasterizer->Draw(pipeline, no_discard);
|
||||
latch.Signal();
|
||||
});
|
||||
latch.Wait();
|
||||
@ -253,12 +255,13 @@ TEST(
|
||||
|
||||
rasterizer->Setup(std::move(surface));
|
||||
|
||||
auto pipeline = fml::AdoptRef(new Pipeline<LayerTree>(/*depth=*/10));
|
||||
auto layer_tree = std::make_unique<LayerTree>(/*frame_size=*/SkISize(),
|
||||
/*device_pixel_ratio=*/2.0f);
|
||||
auto layer_tree_holder = std::make_unique<LayerTreeHolder>();
|
||||
layer_tree_holder->PushIfNewer(std::move(std::move(layer_tree)));
|
||||
bool result = pipeline->Produce().Complete(std::move(layer_tree));
|
||||
EXPECT_TRUE(result);
|
||||
auto no_discard = [](LayerTree&) { return false; };
|
||||
rasterizer->Draw(std::move(layer_tree_holder), no_discard);
|
||||
rasterizer->Draw(pipeline, no_discard);
|
||||
}
|
||||
|
||||
TEST(RasterizerTest, externalViewEmbedderDoesntEndFrameWhenNoSurfaceIsSet) {
|
||||
@ -289,8 +292,9 @@ TEST(RasterizerTest, externalViewEmbedderDoesntEndFrameWhenNoSurfaceIsSet) {
|
||||
|
||||
fml::AutoResetWaitableEvent latch;
|
||||
thread_host.raster_thread->GetTaskRunner()->PostTask([&] {
|
||||
auto pipeline = fml::AdoptRef(new Pipeline<LayerTree>(/*depth=*/10));
|
||||
auto no_discard = [](LayerTree&) { return false; };
|
||||
rasterizer->Draw(std::make_unique<LayerTreeHolder>(), no_discard);
|
||||
rasterizer->Draw(pipeline, no_discard);
|
||||
latch.Signal();
|
||||
});
|
||||
latch.Wait();
|
||||
|
||||
@ -372,7 +372,6 @@ Shell::Shell(DartVMRef vm,
|
||||
vm_(std::move(vm)),
|
||||
is_gpu_disabled_sync_switch_(new fml::SyncSwitch(is_gpu_disabled)),
|
||||
volatile_path_tracker_(std::move(volatile_path_tracker)),
|
||||
pending_draw_semaphore_(1),
|
||||
weak_factory_gpu_(nullptr),
|
||||
weak_factory_(this) {
|
||||
FML_CHECK(vm_) << "Must have access to VM to create a shell.";
|
||||
@ -1139,7 +1138,7 @@ void Shell::OnAnimatorNotifyIdle(int64_t deadline) {
|
||||
}
|
||||
|
||||
// |Animator::Delegate|
|
||||
void Shell::OnAnimatorDraw(std::shared_ptr<LayerTreeHolder> layer_tree_holder,
|
||||
void Shell::OnAnimatorDraw(fml::RefPtr<Pipeline<flutter::LayerTree>> pipeline,
|
||||
fml::TimePoint frame_target_time) {
|
||||
FML_DCHECK(is_setup_);
|
||||
|
||||
@ -1159,28 +1158,19 @@ void Shell::OnAnimatorDraw(std::shared_ptr<LayerTreeHolder> layer_tree_holder,
|
||||
tree.frame_size() != expected_frame_size_;
|
||||
};
|
||||
|
||||
if (!pending_draw_semaphore_.TryWait()) {
|
||||
// Multiple calls to OnAnimatorDraw will still result in a
|
||||
// single request to the Rasterizer::Draw.
|
||||
return;
|
||||
}
|
||||
|
||||
task_runners_.GetRasterTaskRunner()->PostTask(
|
||||
[&pending_draw_semaphore = pending_draw_semaphore_,
|
||||
&waiting_for_first_frame = waiting_for_first_frame_,
|
||||
[&waiting_for_first_frame = waiting_for_first_frame_,
|
||||
&waiting_for_first_frame_condition = waiting_for_first_frame_condition_,
|
||||
rasterizer = rasterizer_->GetWeakPtr(),
|
||||
discard_callback = std::move(discard_callback),
|
||||
layer_tree_holder = std::move(layer_tree_holder)]() {
|
||||
rasterizer = rasterizer_->GetWeakPtr(), pipeline = std::move(pipeline),
|
||||
discard_callback = std::move(discard_callback)]() {
|
||||
if (rasterizer) {
|
||||
rasterizer->Draw(std::move(layer_tree_holder),
|
||||
std::move(discard_callback));
|
||||
rasterizer->Draw(pipeline, std::move(discard_callback));
|
||||
|
||||
if (waiting_for_first_frame.load()) {
|
||||
waiting_for_first_frame.store(false);
|
||||
waiting_for_first_frame_condition.notify_all();
|
||||
}
|
||||
}
|
||||
pending_draw_semaphore.Signal();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -35,7 +35,6 @@
|
||||
#include "flutter/shell/common/animator.h"
|
||||
#include "flutter/shell/common/display_manager.h"
|
||||
#include "flutter/shell/common/engine.h"
|
||||
#include "flutter/shell/common/layer_tree_holder.h"
|
||||
#include "flutter/shell/common/platform_view.h"
|
||||
#include "flutter/shell/common/rasterizer.h"
|
||||
#include "flutter/shell/common/shell_io_manager.h"
|
||||
@ -385,9 +384,6 @@ class Shell final : public PlatformView::Delegate,
|
||||
std::mutex waiting_for_first_frame_mutex_;
|
||||
std::condition_variable waiting_for_first_frame_condition_;
|
||||
|
||||
// Signalled when draw task on the raster thread is complete.
|
||||
fml::Semaphore pending_draw_semaphore_;
|
||||
|
||||
// Written in the UI thread and read from the raster thread. Hence make it
|
||||
// atomic.
|
||||
std::atomic<bool> needs_report_timings_{false};
|
||||
@ -521,7 +517,7 @@ class Shell final : public PlatformView::Delegate,
|
||||
void OnAnimatorNotifyIdle(int64_t deadline) override;
|
||||
|
||||
// |Animator::Delegate|
|
||||
void OnAnimatorDraw(std::shared_ptr<LayerTreeHolder> layer_tree_holder,
|
||||
void OnAnimatorDraw(fml::RefPtr<Pipeline<flutter::LayerTree>> pipeline,
|
||||
fml::TimePoint frame_target_time) override;
|
||||
|
||||
// |Animator::Delegate|
|
||||
|
||||
@ -166,7 +166,7 @@ void ShellTest::PumpOneFrame(Shell* shell,
|
||||
flutter::ViewportMetrics viewport_metrics,
|
||||
LayerTreeBuilder builder) {
|
||||
// Set viewport to nonempty, and call Animator::BeginFrame to make the layer
|
||||
// tree holder nonempty. Without either of this, the layer tree below
|
||||
// tree pipeline nonempty. Without either of this, the layer tree below
|
||||
// won't be rasterized.
|
||||
fml::AutoResetWaitableEvent latch;
|
||||
shell->GetTaskRunners().GetUITaskRunner()->PostTask(
|
||||
@ -200,12 +200,6 @@ void ShellTest::PumpOneFrame(Shell* shell,
|
||||
latch.Signal();
|
||||
});
|
||||
latch.Wait();
|
||||
|
||||
// Wait for render to finish.
|
||||
latch.Reset();
|
||||
shell->GetTaskRunners().GetRasterTaskRunner()->PostTask(
|
||||
[&latch]() { latch.Signal(); });
|
||||
latch.Wait();
|
||||
}
|
||||
|
||||
void ShellTest::DispatchFakePointerData(Shell* shell) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user