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