Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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