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 | #include "flutter/flow/instrumentation.h" |
6 | |
7 | #include <algorithm> |
8 | #include <limits> |
9 | |
10 | #include "third_party/skia/include/core/SkPath.h" |
11 | #include "third_party/skia/include/core/SkSurface.h" |
12 | |
13 | namespace flutter { |
14 | |
15 | static const size_t kMaxSamples = 120; |
16 | static const size_t kMaxFrameMarkers = 8; |
17 | |
18 | Stopwatch::Stopwatch(fml::Milliseconds frame_budget) |
19 | : start_(fml::TimePoint::Now()), current_sample_(0) { |
20 | const fml::TimeDelta delta = fml::TimeDelta::Zero(); |
21 | laps_.resize(kMaxSamples, delta); |
22 | cache_dirty_ = true; |
23 | prev_drawn_sample_index_ = 0; |
24 | frame_budget_ = frame_budget; |
25 | } |
26 | |
27 | Stopwatch::~Stopwatch() = default; |
28 | |
29 | void Stopwatch::Start() { |
30 | start_ = fml::TimePoint::Now(); |
31 | current_sample_ = (current_sample_ + 1) % kMaxSamples; |
32 | } |
33 | |
34 | void Stopwatch::Stop() { |
35 | laps_[current_sample_] = fml::TimePoint::Now() - start_; |
36 | } |
37 | |
38 | void Stopwatch::SetLapTime(const fml::TimeDelta& delta) { |
39 | current_sample_ = (current_sample_ + 1) % kMaxSamples; |
40 | laps_[current_sample_] = delta; |
41 | } |
42 | |
43 | const fml::TimeDelta& Stopwatch::LastLap() const { |
44 | return laps_[(current_sample_ - 1) % kMaxSamples]; |
45 | } |
46 | |
47 | double Stopwatch::UnitFrameInterval(double raster_time_ms) const { |
48 | return raster_time_ms / frame_budget_.count(); |
49 | } |
50 | |
51 | double Stopwatch::UnitHeight(double raster_time_ms, |
52 | double max_unit_interval) const { |
53 | double unitHeight = UnitFrameInterval(raster_time_ms) / max_unit_interval; |
54 | if (unitHeight > 1.0) { |
55 | unitHeight = 1.0; |
56 | } |
57 | return unitHeight; |
58 | } |
59 | |
60 | fml::TimeDelta Stopwatch::MaxDelta() const { |
61 | fml::TimeDelta max_delta; |
62 | for (size_t i = 0; i < kMaxSamples; i++) { |
63 | if (laps_[i] > max_delta) { |
64 | max_delta = laps_[i]; |
65 | } |
66 | } |
67 | return max_delta; |
68 | } |
69 | |
70 | fml::TimeDelta Stopwatch::AverageDelta() const { |
71 | fml::TimeDelta sum; // default to 0 |
72 | for (size_t i = 0; i < kMaxSamples; i++) { |
73 | sum = sum + laps_[i]; |
74 | } |
75 | return sum / kMaxSamples; |
76 | } |
77 | |
78 | // Initialize the SkSurface for drawing into. Draws the base background and any |
79 | // timing data from before the initial Visualize() call. |
80 | void Stopwatch::InitVisualizeSurface(const SkRect& rect) const { |
81 | if (!cache_dirty_) { |
82 | return; |
83 | } |
84 | cache_dirty_ = false; |
85 | |
86 | // TODO(garyq): Use a GPU surface instead of a CPU surface. |
87 | visualize_cache_surface_ = |
88 | SkSurface::MakeRasterN32Premul(rect.width(), rect.height()); |
89 | |
90 | SkCanvas* cache_canvas = visualize_cache_surface_->getCanvas(); |
91 | |
92 | // Establish the graph position. |
93 | const SkScalar x = 0; |
94 | const SkScalar y = 0; |
95 | const SkScalar width = rect.width(); |
96 | const SkScalar height = rect.height(); |
97 | |
98 | SkPaint paint; |
99 | paint.setColor(0x99FFFFFF); |
100 | cache_canvas->drawRect(SkRect::MakeXYWH(x, y, width, height), paint); |
101 | |
102 | // Scale the graph to show frame times up to those that are 3 times the frame |
103 | // time. |
104 | const double one_frame_ms = frame_budget_.count(); |
105 | const double max_interval = one_frame_ms * 3.0; |
106 | const double max_unit_interval = UnitFrameInterval(max_interval); |
107 | |
108 | // Draw the old data to initially populate the graph. |
109 | // Prepare a path for the data. We start at the height of the last point, so |
110 | // it looks like we wrap around |
111 | SkPath path; |
112 | path.setIsVolatile(true); |
113 | path.moveTo(x, height); |
114 | path.lineTo(x, y + height * (1.0 - UnitHeight(laps_[0].ToMillisecondsF(), |
115 | max_unit_interval))); |
116 | double unit_x; |
117 | double unit_next_x = 0.0; |
118 | for (size_t i = 0; i < kMaxSamples; i += 1) { |
119 | unit_x = unit_next_x; |
120 | unit_next_x = (static_cast<double>(i + 1) / kMaxSamples); |
121 | const double sample_y = |
122 | y + height * (1.0 - UnitHeight(laps_[i].ToMillisecondsF(), |
123 | max_unit_interval)); |
124 | path.lineTo(x + width * unit_x, sample_y); |
125 | path.lineTo(x + width * unit_next_x, sample_y); |
126 | } |
127 | path.lineTo( |
128 | width, |
129 | y + height * (1.0 - UnitHeight(laps_[kMaxSamples - 1].ToMillisecondsF(), |
130 | max_unit_interval))); |
131 | path.lineTo(width, height); |
132 | path.close(); |
133 | |
134 | // Draw the graph. |
135 | paint.setColor(0xAA0000FF); |
136 | cache_canvas->drawPath(path, paint); |
137 | } |
138 | |
139 | void Stopwatch::Visualize(SkCanvas* canvas, const SkRect& rect) const { |
140 | // Initialize visualize cache if it has not yet been initialized. |
141 | InitVisualizeSurface(rect); |
142 | |
143 | SkCanvas* cache_canvas = visualize_cache_surface_->getCanvas(); |
144 | SkPaint paint; |
145 | |
146 | // Establish the graph position. |
147 | const SkScalar x = 0; |
148 | const SkScalar y = 0; |
149 | const SkScalar width = rect.width(); |
150 | const SkScalar height = rect.height(); |
151 | |
152 | // Scale the graph to show frame times up to those that are 3 times the frame |
153 | // time. |
154 | const double one_frame_ms = frame_budget_.count(); |
155 | const double max_interval = one_frame_ms * 3.0; |
156 | const double max_unit_interval = UnitFrameInterval(max_interval); |
157 | |
158 | const double sample_unit_width = (1.0 / kMaxSamples); |
159 | |
160 | // Draw vertical replacement bar to erase old/stale pixels. |
161 | paint.setColor(0x99FFFFFF); |
162 | paint.setStyle(SkPaint::Style::kFill_Style); |
163 | paint.setBlendMode(SkBlendMode::kSrc); |
164 | double sample_x = |
165 | x + width * (static_cast<double>(prev_drawn_sample_index_) / kMaxSamples); |
166 | const auto eraser_rect = SkRect::MakeLTRB( |
167 | sample_x, y, sample_x + width * sample_unit_width, height); |
168 | cache_canvas->drawRect(eraser_rect, paint); |
169 | |
170 | // Draws blue timing bar for new data. |
171 | paint.setColor(0xAA0000FF); |
172 | paint.setBlendMode(SkBlendMode::kSrcOver); |
173 | const auto bar_rect = SkRect::MakeLTRB( |
174 | sample_x, |
175 | y + height * (1.0 - |
176 | UnitHeight(laps_[current_sample_ == 0 ? kMaxSamples - 1 |
177 | : current_sample_ - 1] |
178 | .ToMillisecondsF(), |
179 | max_unit_interval)), |
180 | sample_x + width * sample_unit_width, height); |
181 | cache_canvas->drawRect(bar_rect, paint); |
182 | |
183 | // Draw horizontal frame markers. |
184 | paint.setStrokeWidth(0); // hairline |
185 | paint.setStyle(SkPaint::Style::kStroke_Style); |
186 | paint.setColor(0xCC000000); |
187 | |
188 | if (max_interval > one_frame_ms) { |
189 | // Paint the horizontal markers |
190 | size_t frame_marker_count = |
191 | static_cast<size_t>(max_interval / one_frame_ms); |
192 | |
193 | // Limit the number of markers displayed. After a certain point, the graph |
194 | // becomes crowded |
195 | if (frame_marker_count > kMaxFrameMarkers) { |
196 | frame_marker_count = 1; |
197 | } |
198 | |
199 | for (size_t frame_index = 0; frame_index < frame_marker_count; |
200 | frame_index++) { |
201 | const double frame_height = |
202 | height * (1.0 - (UnitFrameInterval((frame_index + 1) * one_frame_ms) / |
203 | max_unit_interval)); |
204 | cache_canvas->drawLine(x, y + frame_height, width, y + frame_height, |
205 | paint); |
206 | } |
207 | } |
208 | |
209 | // Paint the vertical marker for the current frame. |
210 | // We paint it over the current frame, not after it, because when we |
211 | // paint this we don't yet have all the times for the current frame. |
212 | paint.setStyle(SkPaint::Style::kFill_Style); |
213 | paint.setBlendMode(SkBlendMode::kSrcOver); |
214 | if (UnitFrameInterval(LastLap().ToMillisecondsF()) > 1.0) { |
215 | // budget exceeded |
216 | paint.setColor(SK_ColorRED); |
217 | } else { |
218 | // within budget |
219 | paint.setColor(SK_ColorGREEN); |
220 | } |
221 | sample_x = x + width * (static_cast<double>(current_sample_) / kMaxSamples); |
222 | const auto marker_rect = SkRect::MakeLTRB( |
223 | sample_x, y, sample_x + width * sample_unit_width, height); |
224 | cache_canvas->drawRect(marker_rect, paint); |
225 | prev_drawn_sample_index_ = current_sample_; |
226 | |
227 | // Draw the cached surface onto the output canvas. |
228 | paint.reset(); |
229 | visualize_cache_surface_->draw(canvas, rect.x(), rect.y(), &paint); |
230 | } |
231 | |
232 | CounterValues::CounterValues() : current_sample_(kMaxSamples - 1) { |
233 | values_.resize(kMaxSamples, 0); |
234 | } |
235 | |
236 | CounterValues::~CounterValues() = default; |
237 | |
238 | void CounterValues::Add(int64_t value) { |
239 | current_sample_ = (current_sample_ + 1) % kMaxSamples; |
240 | values_[current_sample_] = value; |
241 | } |
242 | |
243 | void CounterValues::Visualize(SkCanvas* canvas, const SkRect& rect) const { |
244 | size_t max_bytes = GetMaxValue(); |
245 | |
246 | if (max_bytes == 0) { |
247 | // The backend for this counter probably did not fill in any values. |
248 | return; |
249 | } |
250 | |
251 | size_t min_bytes = GetMinValue(); |
252 | |
253 | SkPaint paint; |
254 | |
255 | // Paint the background. |
256 | paint.setColor(0x99FFFFFF); |
257 | canvas->drawRect(rect, paint); |
258 | |
259 | // Establish the graph position. |
260 | const SkScalar x = rect.x(); |
261 | const SkScalar y = rect.y(); |
262 | const SkScalar width = rect.width(); |
263 | const SkScalar height = rect.height(); |
264 | const SkScalar bottom = y + height; |
265 | const SkScalar right = x + width; |
266 | |
267 | // Prepare a path for the data. |
268 | SkPath path; |
269 | path.moveTo(x, bottom); |
270 | |
271 | for (size_t i = 0; i < kMaxSamples; ++i) { |
272 | int64_t current_bytes = values_[i]; |
273 | double ratio = static_cast<double>(current_bytes - min_bytes) / |
274 | static_cast<double>(max_bytes - min_bytes); |
275 | path.lineTo( |
276 | x + ((static_cast<double>(i) / static_cast<double>(kMaxSamples)) * |
277 | width), |
278 | y + ((1.0 - ratio) * height)); |
279 | } |
280 | |
281 | path.rLineTo(100, 0); |
282 | path.lineTo(right, bottom); |
283 | path.close(); |
284 | |
285 | // Draw the graph. |
286 | paint.setColor(0xAA0000FF); |
287 | canvas->drawPath(path, paint); |
288 | |
289 | // Paint the vertical marker for the current frame. |
290 | const double sample_unit_width = (1.0 / kMaxSamples); |
291 | const double sample_margin_unit_width = sample_unit_width / 6.0; |
292 | const double sample_margin_width = width * sample_margin_unit_width; |
293 | paint.setStyle(SkPaint::Style::kFill_Style); |
294 | paint.setColor(SK_ColorGRAY); |
295 | double sample_x = |
296 | x + width * (static_cast<double>(current_sample_) / kMaxSamples) - |
297 | sample_margin_width; |
298 | const auto marker_rect = SkRect::MakeLTRB( |
299 | sample_x, y, |
300 | sample_x + width * sample_unit_width + sample_margin_width * 2, bottom); |
301 | canvas->drawRect(marker_rect, paint); |
302 | } |
303 | |
304 | int64_t CounterValues::GetCurrentValue() const { |
305 | return values_[current_sample_]; |
306 | } |
307 | |
308 | int64_t CounterValues::GetMaxValue() const { |
309 | auto max = std::numeric_limits<int64_t>::min(); |
310 | for (size_t i = 0; i < kMaxSamples; ++i) { |
311 | max = std::max<int64_t>(max, values_[i]); |
312 | } |
313 | return max; |
314 | } |
315 | |
316 | int64_t CounterValues::GetMinValue() const { |
317 | auto min = std::numeric_limits<int64_t>::max(); |
318 | for (size_t i = 0; i < kMaxSamples; ++i) { |
319 | min = std::min<int64_t>(min, values_[i]); |
320 | } |
321 | return min; |
322 | } |
323 | |
324 | } // namespace flutter |
325 | |