1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#ifndef FLUTTER_SHELL_COMMON_PIPELINE_H_
6#define FLUTTER_SHELL_COMMON_PIPELINE_H_
7
8#include <deque>
9#include <memory>
10#include <mutex>
11
12#include "flutter/flow/frame_timings.h"
13#include "flutter/flow/layers/layer_tree.h"
14#include "flutter/fml/macros.h"
15#include "flutter/fml/memory/ref_counted.h"
16#include "flutter/fml/synchronization/semaphore.h"
17#include "flutter/fml/trace_event.h"
18
19namespace flutter {
20
21struct PipelineProduceResult {
22 // Whether the item was successfully pushed into the pipeline.
23 bool success = false;
24 // Whether it is the first item of the pipeline. Only valid when 'success' is
25 // 'true'.
26 bool is_first_item = false;
27};
28
29enum class PipelineConsumeResult {
30 NoneAvailable,
31 Done,
32 MoreAvailable,
33};
34
35size_t GetNextPipelineTraceID();
36
37/// A thread-safe queue of resources for a single consumer and a single
38/// producer, with a maximum queue depth.
39///
40/// Pipelines support two key operations: produce and consume.
41///
42/// The consumer calls |Consume| to wait for a resource to be produced and
43/// consume it when ready.
44///
45/// The producer calls |Produce| to generate a `ProducerContinuation` which
46/// provides a means to enqueue a resource in the pipeline, if the pipeline is
47/// below its maximum depth. When the resource has been prepared, the producer
48/// calls `Complete` on the continuation, which enqueues the resource and
49/// signals the waiting consumer.
50///
51/// Pipelines generate the following tracing information:
52/// * PipelineItem: async flow tracking time taken from the time a producer
53/// calls |Produce| to the time a consumer consumes calls |Consume|.
54/// * PipelineProduce: async flow tracking time taken from the time a producer
55/// calls |Produce| to the time they complete the `ProducerContinuation` with
56/// a resource.
57/// * Pipeline Depth: counter of inflight resource producers.
58///
59/// The primary use of this class is as the frame pipeline used in Flutter's
60/// animator/rasterizer.
61template <class R>
62class Pipeline {
63 public:
64 using Resource = R;
65 using ResourcePtr = std::unique_ptr<Resource>;
66
67 /// Denotes a spot in the pipeline reserved for the producer to finish
68 /// preparing a completed pipeline resource.
69 class ProducerContinuation {
70 public:
71 ProducerContinuation() : trace_id_(0) {}
72
73 ProducerContinuation(ProducerContinuation&& other)
74 : continuation_(other.continuation_), trace_id_(other.trace_id_) {
75 other.continuation_ = nullptr;
76 other.trace_id_ = 0;
77 }
78
79 ProducerContinuation& operator=(ProducerContinuation&& other) {
80 std::swap(continuation_, other.continuation_);
81 std::swap(trace_id_, other.trace_id_);
82 return *this;
83 }
84
85 ~ProducerContinuation() {
86 if (continuation_) {
87 continuation_(nullptr, trace_id_);
88 TRACE_EVENT_ASYNC_END0("flutter", "PipelineProduce", trace_id_);
89 // The continuation is being dropped on the floor. End the flow.
90 TRACE_FLOW_END("flutter", "PipelineItem", trace_id_);
91 TRACE_EVENT_ASYNC_END0("flutter", "PipelineItem", trace_id_);
92 }
93 }
94
95 /// Completes the continuation with the specified resource.
96 [[nodiscard]] PipelineProduceResult Complete(ResourcePtr resource) {
97 PipelineProduceResult result;
98 if (continuation_) {
99 result = continuation_(std::move(resource), trace_id_);
100 continuation_ = nullptr;
101 TRACE_EVENT_ASYNC_END0("flutter", "PipelineProduce", trace_id_);
102 TRACE_FLOW_STEP("flutter", "PipelineItem", trace_id_);
103 }
104 return result;
105 }
106
107 explicit operator bool() const { return continuation_ != nullptr; }
108
109 private:
110 friend class Pipeline;
111 using Continuation =
112 std::function<PipelineProduceResult(ResourcePtr, size_t)>;
113
114 Continuation continuation_;
115 uint64_t trace_id_;
116
117 ProducerContinuation(const Continuation& continuation, uint64_t trace_id)
118 : continuation_(continuation), trace_id_(trace_id) {
119 TRACE_EVENT_ASYNC_BEGIN0_WITH_FLOW_IDS("flutter", "PipelineItem",
120 trace_id_, /*flow_id_count=*/1,
121 /*flow_ids=*/&trace_id);
122 TRACE_FLOW_BEGIN("flutter", "PipelineItem", trace_id_);
123 TRACE_EVENT_ASYNC_BEGIN0("flutter", "PipelineProduce", trace_id_);
124 }
125
126 FML_DISALLOW_COPY_AND_ASSIGN(ProducerContinuation);
127 };
128
129 explicit Pipeline(uint32_t depth)
130 : empty_(depth), available_(0), inflight_(0) {}
131
132 ~Pipeline() = default;
133
134 bool IsValid() const { return empty_.IsValid() && available_.IsValid(); }
135
136 /// Creates a `ProducerContinuation` that a producer can use to add a
137 /// resource to the queue.
138 ///
139 /// If the queue is already at its maximum depth, the `ProducerContinuation`
140 /// is returned with success = false.
141 ProducerContinuation Produce() {
142 if (!empty_.TryWait()) {
143 return {};
144 }
145 ++inflight_;
146 FML_TRACE_COUNTER("flutter", "Pipeline Depth",
147 reinterpret_cast<int64_t>(this), //
148 "frames in flight", inflight_.load() //
149 );
150
151 return ProducerContinuation{
152 std::bind(&Pipeline::ProducerCommit, this, std::placeholders::_1,
153 std::placeholders::_2), // continuation
154 GetNextPipelineTraceID()}; // trace id
155 }
156
157 /// Creates a `ProducerContinuation` that will only push the task if the
158 /// queue is empty.
159 ///
160 /// Prefer using |Produce|. ProducerContinuation returned by this method
161 /// doesn't guarantee that the frame will be rendered.
162 ProducerContinuation ProduceIfEmpty() {
163 if (!empty_.TryWait()) {
164 return {};
165 }
166 ++inflight_;
167 FML_TRACE_COUNTER("flutter", "Pipeline Depth",
168 reinterpret_cast<int64_t>(this), //
169 "frames in flight", inflight_.load() //
170 );
171
172 return ProducerContinuation{
173 std::bind(&Pipeline::ProducerCommitIfEmpty, this, std::placeholders::_1,
174 std::placeholders::_2), // continuation
175 GetNextPipelineTraceID()}; // trace id
176 }
177
178 using Consumer = std::function<void(ResourcePtr)>;
179
180 /// @note Procedure doesn't copy all closures.
181 [[nodiscard]] PipelineConsumeResult Consume(const Consumer& consumer) {
182 if (consumer == nullptr) {
183 return PipelineConsumeResult::NoneAvailable;
184 }
185
186 if (!available_.TryWait()) {
187 return PipelineConsumeResult::NoneAvailable;
188 }
189
190 ResourcePtr resource;
191 size_t trace_id = 0;
192 size_t items_count = 0;
193
194 {
195 std::scoped_lock lock(queue_mutex_);
196 std::tie(resource, trace_id) = std::move(queue_.front());
197 queue_.pop_front();
198 items_count = queue_.size();
199 }
200
201 consumer(std::move(resource));
202
203 empty_.Signal();
204 --inflight_;
205
206 TRACE_FLOW_END("flutter", "PipelineItem", trace_id);
207 TRACE_EVENT_ASYNC_END0("flutter", "PipelineItem", trace_id);
208
209 return items_count > 0 ? PipelineConsumeResult::MoreAvailable
210 : PipelineConsumeResult::Done;
211 }
212
213 private:
214 fml::Semaphore empty_;
215 fml::Semaphore available_;
216 std::atomic<int> inflight_;
217 std::mutex queue_mutex_;
218 std::deque<std::pair<ResourcePtr, size_t>> queue_;
219
220 /// Commits a produced resource to the queue and signals the consumer that a
221 /// resource is available.
222 PipelineProduceResult ProducerCommit(ResourcePtr resource, size_t trace_id) {
223 bool is_first_item = false;
224 {
225 std::scoped_lock lock(queue_mutex_);
226 is_first_item = queue_.empty();
227 queue_.emplace_back(std::move(resource), trace_id);
228 }
229
230 // Ensure the queue mutex is not held as that would be a pessimization.
231 available_.Signal();
232 return {.success = true, .is_first_item = is_first_item};
233 }
234
235 PipelineProduceResult ProducerCommitIfEmpty(ResourcePtr resource,
236 size_t trace_id) {
237 {
238 std::scoped_lock lock(queue_mutex_);
239 if (!queue_.empty()) {
240 // Bail if the queue is not empty, opens up spaces to produce other
241 // frames.
242 empty_.Signal();
243 return {.success = false, .is_first_item = false};
244 }
245 queue_.emplace_back(std::move(resource), trace_id);
246 }
247
248 // Ensure the queue mutex is not held as that would be a pessimization.
249 available_.Signal();
250 return {.success = true, .is_first_item = true};
251 }
252
253 FML_DISALLOW_COPY_AND_ASSIGN(Pipeline);
254};
255
256struct LayerTreeItem {
257 LayerTreeItem(std::unique_ptr<LayerTree> layer_tree,
258 std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder,
259 float device_pixel_ratio)
260 : layer_tree(std::move(layer_tree)),
261 frame_timings_recorder(std::move(frame_timings_recorder)),
262 device_pixel_ratio(device_pixel_ratio) {}
263 std::unique_ptr<LayerTree> layer_tree;
264 std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder;
265 float device_pixel_ratio;
266};
267
268using LayerTreePipeline = Pipeline<LayerTreeItem>;
269
270} // namespace flutter
271
272#endif // FLUTTER_SHELL_COMMON_PIPELINE_H_
273

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com

source code of flutter_engine/flutter/shell/common/pipeline.h