|  | // 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/flow/stopwatch_sk.h" | 
|  | #include "include/core/SkCanvas.h" | 
|  | #include "include/core/SkImageInfo.h" | 
|  | #include "include/core/SkPaint.h" | 
|  | #include "include/core/SkPath.h" | 
|  | #include "include/core/SkSize.h" | 
|  | #include "include/core/SkSurface.h" | 
|  |  | 
|  | namespace flutter { | 
|  |  | 
|  | static const size_t kMaxSamples = 120; | 
|  | static const size_t kMaxFrameMarkers = 8; | 
|  |  | 
|  | void SkStopwatchVisualizer::InitVisualizeSurface(SkISize size) const { | 
|  | // Mark as dirty if the size has changed. | 
|  | if (visualize_cache_surface_) { | 
|  | if (size.width() != visualize_cache_surface_->width() || | 
|  | size.height() != visualize_cache_surface_->height()) { | 
|  | cache_dirty_ = true; | 
|  | }; | 
|  | } | 
|  |  | 
|  | if (!cache_dirty_) { | 
|  | return; | 
|  | } | 
|  | cache_dirty_ = false; | 
|  |  | 
|  | // TODO(garyq): Use a GPU surface instead of a CPU surface. | 
|  | visualize_cache_surface_ = | 
|  | SkSurfaces::Raster(SkImageInfo::MakeN32Premul(size)); | 
|  |  | 
|  | SkCanvas* cache_canvas = visualize_cache_surface_->getCanvas(); | 
|  |  | 
|  | // Establish the graph position. | 
|  | const SkScalar x = 0; | 
|  | const SkScalar y = 0; | 
|  | const SkScalar width = size.width(); | 
|  | const SkScalar height = size.height(); | 
|  |  | 
|  | SkPaint paint; | 
|  | paint.setColor(0x99FFFFFF); | 
|  | cache_canvas->drawRect(SkRect::MakeXYWH(x, y, width, height), paint); | 
|  |  | 
|  | // Scale the graph to show frame times up to those that are 3 times the frame | 
|  | // time. | 
|  | const double one_frame_ms = GetFrameBudget().count(); | 
|  | const double max_interval = one_frame_ms * 3.0; | 
|  | const double max_unit_interval = UnitFrameInterval(max_interval); | 
|  |  | 
|  | // Draw the old data to initially populate the graph. | 
|  | // Prepare a path for the data. We start at the height of the last point, so | 
|  | // it looks like we wrap around | 
|  | SkPath path; | 
|  | path.setIsVolatile(true); | 
|  | path.moveTo(x, height); | 
|  | path.lineTo( | 
|  | x, y + height * (1.0 - UnitHeight(stopwatch_.GetLap(0).ToMillisecondsF(), | 
|  | max_unit_interval))); | 
|  | double unit_x; | 
|  | double unit_next_x = 0.0; | 
|  | for (size_t i = 0; i < kMaxSamples; i += 1) { | 
|  | unit_x = unit_next_x; | 
|  | unit_next_x = (static_cast<double>(i + 1) / kMaxSamples); | 
|  | const double sample_y = | 
|  | y + height * (1.0 - UnitHeight(stopwatch_.GetLap(i).ToMillisecondsF(), | 
|  | max_unit_interval)); | 
|  | path.lineTo(x + width * unit_x, sample_y); | 
|  | path.lineTo(x + width * unit_next_x, sample_y); | 
|  | } | 
|  | path.lineTo( | 
|  | width, | 
|  | y + height * | 
|  | (1.0 - | 
|  | UnitHeight(stopwatch_.GetLap(kMaxSamples - 1).ToMillisecondsF(), | 
|  | max_unit_interval))); | 
|  | path.lineTo(width, height); | 
|  | path.close(); | 
|  |  | 
|  | // Draw the graph. | 
|  | paint.setColor(0xAA0000FF); | 
|  | cache_canvas->drawPath(path, paint); | 
|  | } | 
|  |  | 
|  | void SkStopwatchVisualizer::Visualize(DlCanvas* canvas, | 
|  | const SkRect& rect) const { | 
|  | // Initialize visualize cache if it has not yet been initialized. | 
|  | InitVisualizeSurface(SkISize::Make(rect.width(), rect.height())); | 
|  |  | 
|  | SkCanvas* cache_canvas = visualize_cache_surface_->getCanvas(); | 
|  | SkPaint paint; | 
|  |  | 
|  | // Establish the graph position. | 
|  | const SkScalar x = 0; | 
|  | const SkScalar y = 0; | 
|  | const SkScalar width = visualize_cache_surface_->width(); | 
|  | const SkScalar height = visualize_cache_surface_->height(); | 
|  |  | 
|  | // Scale the graph to show frame times up to those that are 3 times the frame | 
|  | // time. | 
|  | const double one_frame_ms = GetFrameBudget().count(); | 
|  | const double max_interval = one_frame_ms * 3.0; | 
|  | const double max_unit_interval = UnitFrameInterval(max_interval); | 
|  |  | 
|  | const double sample_unit_width = (1.0 / kMaxSamples); | 
|  |  | 
|  | // Draw vertical replacement bar to erase old/stale pixels. | 
|  | paint.setColor(0x99FFFFFF); | 
|  | paint.setStyle(SkPaint::Style::kFill_Style); | 
|  | paint.setBlendMode(SkBlendMode::kSrc); | 
|  | double sample_x = | 
|  | x + width * (static_cast<double>(prev_drawn_sample_index_) / kMaxSamples); | 
|  | const auto eraser_rect = SkRect::MakeLTRB( | 
|  | sample_x, y, sample_x + width * sample_unit_width, height); | 
|  | cache_canvas->drawRect(eraser_rect, paint); | 
|  |  | 
|  | // Draws blue timing bar for new data. | 
|  | paint.setColor(0xAA0000FF); | 
|  | paint.setBlendMode(SkBlendMode::kSrcOver); | 
|  | const auto bar_rect = SkRect::MakeLTRB( | 
|  | sample_x, | 
|  | y + height * | 
|  | (1.0 - | 
|  | UnitHeight(stopwatch_ | 
|  | .GetLap(stopwatch_.GetCurrentSample() == 0 | 
|  | ? kMaxSamples - 1 | 
|  | : stopwatch_.GetCurrentSample() - 1) | 
|  | .ToMillisecondsF(), | 
|  | max_unit_interval)), | 
|  | sample_x + width * sample_unit_width, height); | 
|  | cache_canvas->drawRect(bar_rect, paint); | 
|  |  | 
|  | // Draw horizontal frame markers. | 
|  | paint.setStrokeWidth(0);  // hairline | 
|  | paint.setStyle(SkPaint::Style::kStroke_Style); | 
|  | paint.setColor(0xCC000000); | 
|  |  | 
|  | if (max_interval > one_frame_ms) { | 
|  | // Paint the horizontal markers | 
|  | size_t frame_marker_count = | 
|  | static_cast<size_t>(max_interval / one_frame_ms); | 
|  |  | 
|  | // Limit the number of markers displayed. After a certain point, the graph | 
|  | // becomes crowded | 
|  | if (frame_marker_count > kMaxFrameMarkers) { | 
|  | frame_marker_count = 1; | 
|  | } | 
|  |  | 
|  | for (size_t frame_index = 0; frame_index < frame_marker_count; | 
|  | frame_index++) { | 
|  | const double frame_height = | 
|  | height * (1.0 - (UnitFrameInterval((frame_index + 1) * one_frame_ms) / | 
|  | max_unit_interval)); | 
|  | cache_canvas->drawLine(x, y + frame_height, width, y + frame_height, | 
|  | paint); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Paint the vertical marker for the current frame. | 
|  | // We paint it over the current frame, not after it, because when we | 
|  | // paint this we don't yet have all the times for the current frame. | 
|  | paint.setStyle(SkPaint::Style::kFill_Style); | 
|  | paint.setBlendMode(SkBlendMode::kSrcOver); | 
|  | if (UnitFrameInterval(stopwatch_.LastLap().ToMillisecondsF()) > 1.0) { | 
|  | // budget exceeded | 
|  | paint.setColor(SK_ColorRED); | 
|  | } else { | 
|  | // within budget | 
|  | paint.setColor(SK_ColorGREEN); | 
|  | } | 
|  | sample_x = x + width * (static_cast<double>(stopwatch_.GetCurrentSample()) / | 
|  | kMaxSamples); | 
|  | const auto marker_rect = SkRect::MakeLTRB( | 
|  | sample_x, y, sample_x + width * sample_unit_width, height); | 
|  | cache_canvas->drawRect(marker_rect, paint); | 
|  | prev_drawn_sample_index_ = stopwatch_.GetCurrentSample(); | 
|  |  | 
|  | // Draw the cached surface onto the output canvas. | 
|  | auto image = DlImage::Make(visualize_cache_surface_->makeImageSnapshot()); | 
|  | canvas->DrawImage(image, {rect.x(), rect.y()}, | 
|  | DlImageSampling::kNearestNeighbor); | 
|  | } | 
|  |  | 
|  | }  // namespace flutter |