diff --git a/ci/licenses_golden/excluded_files b/ci/licenses_golden/excluded_files index 968cc72f3e10d..07cfad821a1b2 100644 --- a/ci/licenses_golden/excluded_files +++ b/ci/licenses_golden/excluded_files @@ -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 diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 3ae421ecf59f3..f1de8a48d1b40 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -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 diff --git a/flow/BUILD.gn b/flow/BUILD.gn index 5d69ae2e166fa..53de13474e316 100644 --- a/flow/BUILD.gn +++ b/flow/BUILD.gn @@ -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", diff --git a/flow/layers/performance_overlay_layer.cc b/flow/layers/performance_overlay_layer.cc index 4275a996fdb35..911131fd0cfbc 100644 --- a/flow/layers/performance_overlay_layer.cc +++ b/flow/layers/performance_overlay_layer.cc @@ -6,8 +6,11 @@ #include #include +#include #include +#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 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(stopwatch); + } else { + visualizer = std::make_unique(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_); } diff --git a/flow/stopwatch.cc b/flow/stopwatch.cc index b8e8d3007a6cd..d61a591bd1ce5 100644 --- a/flow/stopwatch.cc +++ b/flow/stopwatch.cc @@ -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_; } diff --git a/flow/stopwatch.h b/flow/stopwatch.h index d6e8f0da7fbcc..5755b8d460b44 100644 --- a/flow/stopwatch.h +++ b/flow/stopwatch.h @@ -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; diff --git a/flow/stopwatch_dl.cc b/flow/stopwatch_dl.cc new file mode 100644 index 0000000000000..20a4958460614 --- /dev/null +++ b/flow/stopwatch_dl.cc @@ -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 +#include +#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(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(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::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{ + 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 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 diff --git a/flow/stopwatch_dl.h b/flow/stopwatch_dl.h new file mode 100644 index 0000000000000..d15dc40b49473 --- /dev/null +++ b/flow/stopwatch_dl.h @@ -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 IntoVertices(); + + private: + std::vector vertices_; + std::vector colors_; +}; + +} // namespace flutter + +#endif // FLUTTER_FLOW_STOPWATCH_DL_H_ diff --git a/flow/stopwatch_dl_unittests.cc b/flow/stopwatch_dl_unittests.cc new file mode 100644 index 0000000000000..f8d2900f84674 --- /dev/null +++ b/flow/stopwatch_dl_unittests.cc @@ -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 diff --git a/flow/stopwatch_unittests.cc b/flow/stopwatch_unittests.cc index 3ded5142a9fbb..50a635285033a 100644 --- a/flow/stopwatch_unittests.cc +++ b/flow/stopwatch_unittests.cc @@ -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