Skip to content

Commit

Permalink
Refactor Stopwatch into StopwatchVisualizer and `SkStopwatchVisua…
Browse files Browse the repository at this point in the history
…lizer` (flutter#45200)

Partial work towards flutter/flutter#126009.

Still working as before:
<img width="1210" alt="Screenshot 2023-08-28 at 6 32 45 PM"
src="https://github.com/flutter/engine/assets/168174/38728015-d0d4-4933-bd31-d2326c76aeee">

(There are some `PerformanceOverlayLayerDefault.*` tests that don't run
locally, so I guess I'll let CI run those)
  • Loading branch information
matanlurey authored Aug 29, 2023
1 parent c8a91b2 commit 99b9e20
Show file tree
Hide file tree
Showing 11 changed files with 302 additions and 190 deletions.
4 changes: 4 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,8 @@ ORIGIN: ../../../flutter/flow/raster_cache_util.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/flow/skia_gpu_object.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/flow/stopwatch.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/flow/stopwatch.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/flow/stopwatch_sk.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/flow/stopwatch_sk.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/flow/surface.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/flow/surface.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/flow/surface_frame.cc + ../../../flutter/LICENSE
Expand Down Expand Up @@ -3592,6 +3594,8 @@ FILE: ../../../flutter/flow/raster_cache_util.h
FILE: ../../../flutter/flow/skia_gpu_object.h
FILE: ../../../flutter/flow/stopwatch.cc
FILE: ../../../flutter/flow/stopwatch.h
FILE: ../../../flutter/flow/stopwatch_sk.cc
FILE: ../../../flutter/flow/stopwatch_sk.h
FILE: ../../../flutter/flow/surface.cc
FILE: ../../../flutter/flow/surface.h
FILE: ../../../flutter/flow/surface_frame.cc
Expand Down
2 changes: 2 additions & 0 deletions flow/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ source_set("flow") {
"skia_gpu_object.h",
"stopwatch.cc",
"stopwatch.h",
"stopwatch_sk.cc",
"stopwatch_sk.h",
"surface.cc",
"surface.h",
"surface_frame.cc",
Expand Down
7 changes: 6 additions & 1 deletion flow/layers/performance_overlay_layer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <iostream>
#include <string>

#include "flow/stopwatch_sk.h"
#include "third_party/skia/include/core/SkFont.h"
#include "third_party/skia/include/core/SkTextBlob.h"

Expand All @@ -29,7 +30,11 @@ void VisualizeStopWatch(DlCanvas* canvas,

if (show_graph) {
SkRect visualization_rect = SkRect::MakeXYWH(x, y, width, height);
stopwatch.Visualize(canvas, visualization_rect);

// TODO(matanlurey): Select a visualizer based on the current backend.
// https://github.com/flutter/flutter/issues/126009
SkStopwatchVisualizer visualizer = SkStopwatchVisualizer(stopwatch);
visualizer.Visualize(canvas, visualization_rect);
}

if (show_labels) {
Expand Down
186 changes: 12 additions & 174 deletions flow/stopwatch.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,16 @@

#include "flutter/flow/stopwatch.h"

#include "include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkPath.h"
#include "third_party/skia/include/core/SkSurface.h"

namespace flutter {

static const size_t kMaxSamples = 120;
static const size_t kMaxFrameMarkers = 8;

Stopwatch::Stopwatch(const RefreshRateUpdater& updater)
: refresh_rate_updater_(updater),
start_(fml::TimePoint::Now()),
current_sample_(0) {
const fml::TimeDelta delta = fml::TimeDelta::Zero();
laps_.resize(kMaxSamples, delta);
cache_dirty_ = true;
prev_drawn_sample_index_ = 0;
}

Stopwatch::~Stopwatch() = default;
Expand Down Expand Up @@ -51,12 +44,20 @@ const fml::TimeDelta& Stopwatch::LastLap() const {
return laps_[(current_sample_ - 1) % kMaxSamples];
}

double Stopwatch::UnitFrameInterval(double raster_time_ms) const {
return raster_time_ms / GetFrameBudget().count();
const fml::TimeDelta& Stopwatch::GetLap(size_t index) const {
return laps_[index];
}

size_t Stopwatch::GetCurrentSample() const {
return current_sample_;
}

double StopwatchVisualizer::UnitFrameInterval(double raster_time_ms) const {
return raster_time_ms / stopwatch_.GetFrameBudget().count();
}

double Stopwatch::UnitHeight(double raster_time_ms,
double max_unit_interval) const {
double StopwatchVisualizer::UnitHeight(double raster_time_ms,
double max_unit_interval) const {
double unit_height = UnitFrameInterval(raster_time_ms) / max_unit_interval;
if (unit_height > 1.0) {
unit_height = 1.0;
Expand All @@ -82,169 +83,6 @@ fml::TimeDelta Stopwatch::AverageDelta() const {
return sum / kMaxSamples;
}

// Initialize the SkSurface for drawing into. Draws the base background and any
// timing data from before the initial Visualize() call.
void Stopwatch::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(laps_[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(laps_[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(laps_[kMaxSamples - 1].ToMillisecondsF(),
max_unit_interval)));
path.lineTo(width, height);
path.close();

// Draw the graph.
paint.setColor(0xAA0000FF);
cache_canvas->drawPath(path, paint);
}

void Stopwatch::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(laps_[current_sample_ == 0 ? kMaxSamples - 1
: current_sample_ - 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(LastLap().ToMillisecondsF()) > 1.0) {
// budget exceeded
paint.setColor(SK_ColorRED);
} else {
// within budget
paint.setColor(SK_ColorGREEN);
}
sample_x = x + width * (static_cast<double>(current_sample_) / 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_ = current_sample_;

// 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);
}

fml::Milliseconds Stopwatch::GetFrameBudget() const {
return refresh_rate_updater_.GetFrameBudget();
}
Expand Down
51 changes: 36 additions & 15 deletions flow/stopwatch.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
#include "flutter/fml/time/time_delta.h"
#include "flutter/fml/time/time_point.h"

#include "third_party/skia/include/core/SkSurface.h"

namespace flutter {

class Stopwatch {
Expand All @@ -32,16 +30,16 @@ class Stopwatch {

~Stopwatch();

const fml::TimeDelta& GetLap(size_t index) const;

size_t GetCurrentSample() const;

const fml::TimeDelta& LastLap() const;

fml::TimeDelta MaxDelta() const;

fml::TimeDelta AverageDelta() const;

void InitVisualizeSurface(SkISize size) const;

void Visualize(DlCanvas* canvas, const SkRect& rect) const;

void Start();

void Stop();
Expand All @@ -52,20 +50,11 @@ class Stopwatch {
fml::Milliseconds GetFrameBudget() const;

private:
inline double UnitFrameInterval(double time_ms) const;
inline double UnitHeight(double time_ms, double max_height) const;

const RefreshRateUpdater& refresh_rate_updater_;
fml::TimePoint start_;
std::vector<fml::TimeDelta> laps_;
size_t current_sample_;

// Mutable data cache for performance optimization of the graphs. Prevents
// expensive redrawing of old data.
mutable bool cache_dirty_;
mutable sk_sp<SkSurface> visualize_cache_surface_;
mutable size_t prev_drawn_sample_index_;

FML_DISALLOW_COPY_AND_ASSIGN(Stopwatch);
};

Expand All @@ -91,6 +80,38 @@ class FixedRefreshRateStopwatch : public Stopwatch {
FixedRefreshRateUpdater fixed_delegate_;
};

//------------------------------------------------------------------------------
/// @brief Abstract class for visualizing (i.e. drawing) a stopwatch.
///
/// @note This was originally folded into the |Stopwatch| class, but
/// was separated out to make it easier to change the underlying
/// implementation (which relied directly on Skia, not showing on
/// Impeller: https://github.com/flutter/flutter/issues/126009).
class StopwatchVisualizer {
public:
explicit StopwatchVisualizer(const Stopwatch& stopwatch)
: stopwatch_(stopwatch) {}

virtual ~StopwatchVisualizer() = default;

/// @brief Renders the stopwatch as a graph.
///
/// @param canvas The canvas to draw on.
/// @param[in] rect The rectangle to draw in.
virtual void Visualize(DlCanvas* canvas, const SkRect& rect) const = 0;

FML_DISALLOW_COPY_AND_ASSIGN(StopwatchVisualizer);

protected:
/// @brief Converts a raster time to a unit interval.
double UnitFrameInterval(double time_ms) const;

/// @brief Converts a raster time to a unit height.
double UnitHeight(double time_ms, double max_height) const;

const Stopwatch& stopwatch_;
};

} // namespace flutter

#endif // FLUTTER_FLOW_INSTRUMENTATION_H_
Loading

0 comments on commit 99b9e20

Please sign in to comment.