Add a DlStopwatchVisualizer and conditionally use it for Impeller (flutter/engine#45259)

Closes https://github.com/flutter/flutter/issues/126009.

One major change, as-per @jonahwilliams's feedback, is that I created a helper class called `DlVertexPainter`, which provides a `DrawRect`-like API, but just builds a buffer of vertices, resulting into just a single:

```cc
// Actually draw.
canvas->DrawVertices(painter.IntoVertices(), DlBlendMode::kSrc, paint);
```

Also, added test for it, since there is no way to screenshot test to Impeller overlay yet.

# Impeller

![Screenshot 2023-08-30 at 2 08 51 PM](https://github.com/flutter/engine/assets/168174/13bb83a7-9a02-40ae-b9e4-d71a2e47b594)
This commit is contained in:
Matan Lurey
2023-08-30 16:15:16 -07:00
committed by GitHub
parent 1410d8beaa
commit 8a55ee5d07
10 changed files with 317 additions and 8 deletions

View File

@@ -74,6 +74,7 @@
../../../flutter/flow/mutators_stack_unittests.cc
../../../flutter/flow/raster_cache_unittests.cc
../../../flutter/flow/skia_gpu_object_unittests.cc
../../../flutter/flow/stopwatch_dl_unittests.cc
../../../flutter/flow/stopwatch_unittests.cc
../../../flutter/flow/surface_frame_unittests.cc
../../../flutter/flow/testing

View File

@@ -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_dl.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/flow/stopwatch_dl.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
@@ -3594,6 +3596,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_dl.cc
FILE: ../../../flutter/flow/stopwatch_dl.h
FILE: ../../../flutter/flow/stopwatch_sk.cc
FILE: ../../../flutter/flow/stopwatch_sk.h
FILE: ../../../flutter/flow/surface.cc

View File

@@ -77,6 +77,8 @@ source_set("flow") {
"skia_gpu_object.h",
"stopwatch.cc",
"stopwatch.h",
"stopwatch_dl.cc",
"stopwatch_dl.h",
"stopwatch_sk.cc",
"stopwatch_sk.h",
"surface.cc",
@@ -168,6 +170,7 @@ if (enable_unittests) {
"mutators_stack_unittests.cc",
"raster_cache_unittests.cc",
"skia_gpu_object_unittests.cc",
"stopwatch_dl_unittests.cc",
"stopwatch_unittests.cc",
"surface_frame_unittests.cc",
"testing/mock_layer_unittests.cc",

View File

@@ -6,8 +6,11 @@
#include <iomanip>
#include <iostream>
#include <memory>
#include <string>
#include "flow/stopwatch.h"
#include "flow/stopwatch_dl.h"
#include "flow/stopwatch_sk.h"
#include "third_party/skia/include/core/SkFont.h"
#include "third_party/skia/include/core/SkTextBlob.h"
@@ -16,6 +19,7 @@ namespace flutter {
namespace {
void VisualizeStopWatch(DlCanvas* canvas,
const bool impeller_enabled,
const Stopwatch& stopwatch,
SkScalar x,
SkScalar y,
@@ -30,11 +34,15 @@ void VisualizeStopWatch(DlCanvas* canvas,
if (show_graph) {
SkRect visualization_rect = SkRect::MakeXYWH(x, y, width, height);
std::unique_ptr<StopwatchVisualizer> visualizer;
// 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 (impeller_enabled) {
visualizer = std::make_unique<DlStopwatchVisualizer>(stopwatch);
} else {
visualizer = std::make_unique<SkStopwatchVisualizer>(stopwatch);
}
visualizer->Visualize(canvas, visualization_rect);
}
if (show_labels) {
@@ -105,12 +113,13 @@ void PerformanceOverlayLayer::Paint(PaintContext& context) const {
auto mutator = context.state_stack.save();
VisualizeStopWatch(
context.canvas, context.raster_time, x, y, width, height - padding,
options_ & kVisualizeRasterizerStatistics,
context.canvas, context.impeller_enabled, context.raster_time, x, y,
width, height - padding, options_ & kVisualizeRasterizerStatistics,
options_ & kDisplayRasterizerStatistics, "Raster", font_path_);
VisualizeStopWatch(context.canvas, context.ui_time, x, y + height, width,
height - padding, options_ & kVisualizeEngineStatistics,
VisualizeStopWatch(context.canvas, context.impeller_enabled, context.ui_time,
x, y + height, width, height - padding,
options_ & kVisualizeEngineStatistics,
options_ & kDisplayEngineStatistics, "UI", font_path_);
}

View File

@@ -48,6 +48,10 @@ const fml::TimeDelta& Stopwatch::GetLap(size_t index) const {
return laps_[index];
}
size_t Stopwatch::GetLapsCount() const {
return laps_.size();
}
size_t Stopwatch::GetCurrentSample() const {
return current_sample_;
}

View File

@@ -32,6 +32,9 @@ class Stopwatch {
const fml::TimeDelta& GetLap(size_t index) const;
/// Return a reference to all the laps.
size_t GetLapsCount() const;
size_t GetCurrentSample() const;
const fml::TimeDelta& LastLap() const;

View File

@@ -0,0 +1,149 @@
// 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_dl.h"
#include <memory>
#include <vector>
#include "display_list/dl_blend_mode.h"
#include "display_list/dl_canvas.h"
#include "display_list/dl_color.h"
#include "display_list/dl_paint.h"
#include "display_list/dl_vertices.h"
#include "include/core/SkRect.h"
namespace flutter {
static const size_t kMaxSamples = 120;
static const size_t kMaxFrameMarkers = 8;
void DlStopwatchVisualizer::Visualize(DlCanvas* canvas,
const SkRect& rect) const {
auto painter = DlVertexPainter();
DlPaint paint;
// Establish the graph position.
auto const x = rect.x();
auto const y = rect.y();
auto const width = rect.width();
auto const height = rect.height();
auto const bottom = rect.bottom();
// Scale the graph to show time frames up to those that are 3x the frame time.
auto const one_frame_ms = stopwatch_.GetFrameBudget().count();
auto const max_interval = one_frame_ms * 3.0;
auto const max_unit_interval = UnitFrameInterval(max_interval);
auto const sample_unit_width = (1.0 / kMaxSamples);
// Provide a semi-transparent background for the graph.
painter.DrawRect(rect, 0x99FFFFFF);
// Prepare a path for the data; we start at the height of the last point so
// it looks like we wrap around.
{
for (auto i = size_t(0); i < stopwatch_.GetLapsCount(); i++) {
auto const sample_unit_height =
(1.0 - UnitHeight(stopwatch_.GetLap(i).ToMillisecondsF(),
max_unit_interval));
auto const bar_width = width * sample_unit_width;
auto const bar_height = height * sample_unit_height;
auto const bar_left = x + width * sample_unit_width * i;
painter.DrawRect(SkRect::MakeLTRB(/*left=*/bar_left,
/*top=*/y + bar_height,
/*right=*/bar_left + bar_width,
/*bottom=*/bottom),
0xAA0000FF);
}
}
// Draw horizontal frame markers.
{
if (max_interval > one_frame_ms) {
// Paint the horizontal markers.
auto count = static_cast<size_t>(max_interval / one_frame_ms);
// Limit the number of markers to a reasonable amount.
if (count > kMaxFrameMarkers) {
count = 1;
}
for (auto i = size_t(0); i < count; i++) {
auto const frame_height =
height * (1.0 - (UnitFrameInterval(i + 1) * one_frame_ms) /
max_unit_interval);
// Draw a skinny rectangle (i.e. a line).
painter.DrawRect(SkRect::MakeLTRB(/*left=*/x,
/*top=*/y + frame_height,
/*right=*/width,
/*bottom=*/y + frame_height + 1),
0xCC000000);
}
}
}
// Paint the vertical marker for the current frame.
{
DlColor color = DlColor::kGreen();
if (UnitFrameInterval(stopwatch_.LastLap().ToMillisecondsF()) > 1.0) {
// budget exceeded.
color = DlColor::kRed();
}
auto const l =
x + width * (static_cast<double>(stopwatch_.GetCurrentSample()) /
kMaxSamples);
auto const t = y;
auto const r = l + width * sample_unit_width;
auto const b = rect.bottom();
painter.DrawRect(SkRect::MakeLTRB(l, t, r, b), color);
}
// Actually draw.
// Note we use kSrcOver, because some of the colors above have opacity < 1.0.
canvas->DrawVertices(painter.IntoVertices(), DlBlendMode::kSrcOver, paint);
}
void DlVertexPainter::DrawRect(const SkRect& rect, const DlColor& color) {
// Draw 6 vertices representing 2 triangles.
auto const left = rect.x();
auto const top = rect.y();
auto const right = rect.right();
auto const bottom = rect.bottom();
auto const vertices = std::array<SkPoint, 6>{
SkPoint::Make(left, top), // tl tr
SkPoint::Make(right, top), // br
SkPoint::Make(right, bottom), //
SkPoint::Make(right, bottom), // tl
SkPoint::Make(left, bottom), // bl br
SkPoint::Make(left, top) //
};
auto const colors = std::array<DlColor, 6>{
color, // tl tr
color, // br
color, //
color, // tl
color, // bl br
color //
};
vertices_.insert(vertices_.end(), vertices.begin(), vertices.end());
colors_.insert(colors_.end(), colors.begin(), colors.end());
}
std::shared_ptr<DlVertices> DlVertexPainter::IntoVertices() {
auto const result = DlVertices::Make(
/*mode=*/DlVertexMode::kTriangles,
/*vertex_count=*/vertices_.size(),
/*vertices=*/vertices_.data(),
/*texture_coordinates=*/nullptr,
/*colors=*/colors_.data());
vertices_.clear();
colors_.clear();
return result;
}
} // namespace flutter

View File

@@ -0,0 +1,56 @@
// 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.
#ifndef FLUTTER_FLOW_STOPWATCH_DL_H_
#define FLUTTER_FLOW_STOPWATCH_DL_H_
#include "flow/stopwatch.h"
namespace flutter {
//------------------------------------------------------------------------------
/// A stopwatch visualizer that uses DisplayList (|DlCanvas|) to draw.
///
/// @note This is the newer non-backend specific version, that works in both
/// Skia and Impeller. The older Skia-specific version is
/// |SkStopwatchVisualizer|, which still should be used for Skia-specific
/// optimizations.
class DlStopwatchVisualizer : public StopwatchVisualizer {
public:
explicit DlStopwatchVisualizer(const Stopwatch& stopwatch)
: StopwatchVisualizer(stopwatch) {}
void Visualize(DlCanvas* canvas, const SkRect& rect) const override;
};
/// @brief Provides canvas-like painting methods that actually build vertices.
///
/// The goal is minimally invasive rendering for the performance monitor.
///
/// The methods in this class are intended to be used by |DlStopwatchVisualizer|
/// only. The rationale is the creating lines, rectangles, and paths (while OK
/// for general apps) would cause non-trivial work for the performance monitor
/// due to tessellation per-frame.
///
/// @note A goal of this class was to make updating the performance monitor
/// (and keeping it in sync with the |SkStopwatchVisualizer|) as easy as
/// possible (i.e. not having to do triangle-math).
class DlVertexPainter final {
public:
/// Draws a rectangle with the given color to a buffer.
void DrawRect(const SkRect& rect, const DlColor& color);
/// Converts the buffered vertices into a |DlVertices| object.
///
/// @note This method clears the buffer.
std::shared_ptr<DlVertices> IntoVertices();
private:
std::vector<SkPoint> vertices_;
std::vector<DlColor> colors_;
};
} // namespace flutter
#endif // FLUTTER_FLOW_STOPWATCH_DL_H_

View File

@@ -0,0 +1,73 @@
// 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_dl.h"
#include "gtest/gtest.h"
namespace flutter {
namespace testing {
static SkRect MakeRectFromVertices(SkPoint vertices[6]) {
// "Combine" the vertices to form a rectangle.
auto const left = std::min(vertices[0].x(), vertices[5].x());
auto const top = std::min(vertices[0].y(), vertices[1].y());
auto const right = std::max(vertices[1].x(), vertices[2].x());
auto const bottom = std::max(vertices[2].y(), vertices[3].y());
return SkRect::MakeLTRB(left, top, right, bottom);
}
TEST(DlVertexPainter, DrawRectIntoVertices) {
auto painter = DlVertexPainter();
// Paint a red rectangle.
painter.DrawRect(SkRect::MakeLTRB(0, 0, 10, 10), DlColor::kRed());
// Paint a blue rectangle.
painter.DrawRect(SkRect::MakeLTRB(10, 10, 20, 20), DlColor::kBlue());
// Convert the buffered vertices into a |DlVertices| object.
auto vertices = painter.IntoVertices();
// Verify the vertices.
EXPECT_EQ(vertices->mode(), DlVertexMode::kTriangles);
EXPECT_EQ(vertices->vertex_count(), 3 * 2 * 2);
auto const points = vertices->vertices();
{
// Extract the first 6 vertices (first rectangle).
SkPoint first_rect_vertices[6];
std::copy(points, points + 6, first_rect_vertices);
EXPECT_EQ(MakeRectFromVertices(first_rect_vertices),
SkRect::MakeLTRB(0, 0, 10, 10));
}
{
// Extract the next 6 vertices (second rectangle).
SkPoint second_rect_vertices[6];
std::copy(points + 6, points + 12, second_rect_vertices);
EXPECT_EQ(MakeRectFromVertices(second_rect_vertices),
SkRect::MakeLTRB(10, 10, 20, 20));
}
// Verify the colors (first 6 vertices are red, next 6 are blue).
auto const colors = vertices->colors();
EXPECT_EQ(colors[0], DlColor::kRed());
EXPECT_EQ(colors[1], DlColor::kRed());
EXPECT_EQ(colors[2], DlColor::kRed());
EXPECT_EQ(colors[3], DlColor::kRed());
EXPECT_EQ(colors[4], DlColor::kRed());
EXPECT_EQ(colors[5], DlColor::kRed());
EXPECT_EQ(colors[6], DlColor::kBlue());
EXPECT_EQ(colors[7], DlColor::kBlue());
EXPECT_EQ(colors[8], DlColor::kBlue());
EXPECT_EQ(colors[9], DlColor::kBlue());
EXPECT_EQ(colors[10], DlColor::kBlue());
EXPECT_EQ(colors[11], DlColor::kBlue());
}
} // namespace testing
} // namespace flutter

View File

@@ -57,5 +57,12 @@ TEST(Instrumentation, GetCurrentSampleTest) {
EXPECT_EQ(stopwatch.GetCurrentSample(), size_t(1));
}
TEST(Instrumentation, GetLapsCount) {
fml::Milliseconds frame_budget_90fps = fml::RefreshRateToFrameBudget(90);
FixedRefreshRateStopwatch stopwatch(frame_budget_90fps);
stopwatch.SetLapTime(fml::TimeDelta::FromMilliseconds(10));
EXPECT_EQ(stopwatch.GetLapsCount(), size_t(120));
}
} // namespace testing
} // namespace flutter