1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "flutter/flow/layers/performance_overlay_layer.h"
6
7#include "flutter/flow/flow_test_utils.h"
8#include "flutter/flow/raster_cache.h"
9#include "flutter/flow/testing/layer_test.h"
10#include "flutter/flow/testing/mock_layer.h"
11#include "flutter/fml/build_config.h"
12#include "flutter/fml/macros.h"
13#include "flutter/testing/mock_canvas.h"
14#include "gtest/gtest.h"
15#include "third_party/skia/include/core/SkData.h"
16#include "third_party/skia/include/core/SkSerialProcs.h"
17#include "third_party/skia/include/core/SkSurface.h"
18#include "third_party/skia/include/core/SkTextBlob.h"
19#include "third_party/skia/include/utils/SkBase64.h"
20
21#include <cstdint>
22#include <sstream>
23
24namespace flutter {
25namespace testing {
26namespace {
27
28// To get the size of kMockedTimes in compile time.
29template <class T, std::size_t N>
30constexpr int size(const T (&array)[N]) noexcept {
31 return N;
32}
33
34constexpr int kMockedTimes[] = {17, 1, 4, 24, 4, 25, 30, 4, 13, 34,
35 14, 0, 18, 9, 32, 36, 26, 23, 5, 8,
36 32, 18, 29, 16, 29, 18, 0, 36, 33, 10};
37
38static std::string GetGoldenFilePath(int refresh_rate, bool is_new) {
39 std::stringstream ss;
40 // This unit test should only be run on Linux (not even on Mac since it's a
41 // golden test). Hence we don't have to worry about the "/" vs. "\".
42 ss << flutter::GetGoldenDir() << "/"
43 << "performance_overlay_gold_" << refresh_rate << "fps"
44 << (is_new ? "_new" : "") << ".png";
45 return ss.str();
46}
47
48static void TestPerformanceOverlayLayerGold(int refresh_rate) {
49 std::string golden_file_path = GetGoldenFilePath(refresh_rate, false);
50 std::string new_golden_file_path = GetGoldenFilePath(refresh_rate, true);
51
52 flutter::Stopwatch mock_stopwatch(
53 fml::RefreshRateToFrameBudget(refresh_rate));
54 for (int i = 0; i < size(kMockedTimes); ++i) {
55 mock_stopwatch.SetLapTime(
56 fml::TimeDelta::FromMilliseconds(kMockedTimes[i]));
57 }
58
59 const SkImageInfo image_info = SkImageInfo::MakeN32Premul(1000, 1000);
60 sk_sp<SkSurface> surface = SkSurface::MakeRaster(image_info);
61
62 ASSERT_TRUE(surface != nullptr);
63
64 flutter::TextureRegistry unused_texture_registry;
65 flutter::Layer::PaintContext paintContext = {
66 nullptr, surface->getCanvas(), nullptr, nullptr, mock_stopwatch,
67 mock_stopwatch, unused_texture_registry, nullptr, false};
68
69 // Specify font file to ensure the same font across different operation
70 // systems.
71 flutter::PerformanceOverlayLayer layer(
72 flutter::kDisplayRasterizerStatistics |
73 flutter::kVisualizeRasterizerStatistics |
74 flutter::kDisplayEngineStatistics |
75 flutter::kVisualizeEngineStatistics,
76 flutter::GetFontFile().c_str());
77 layer.set_paint_bounds(SkRect::MakeWH(1000, 400));
78 surface->getCanvas()->clear(SK_ColorTRANSPARENT);
79 layer.Paint(paintContext);
80
81 sk_sp<SkImage> snapshot = surface->makeImageSnapshot();
82 sk_sp<SkData> snapshot_data = snapshot->encodeToData();
83
84 sk_sp<SkData> golden_data =
85 SkData::MakeFromFileName(golden_file_path.c_str());
86 EXPECT_TRUE(golden_data != nullptr)
87 << "Golden file not found: " << golden_file_path << ".\n"
88 << "Please either set --golden-dir, or make sure that the unit test is "
89 << "run from the right directory (e.g., flutter/engine/src).";
90
91 // TODO(https://github.com/flutter/flutter/issues/53784): enable this on all
92 // platforms.
93#if !defined(OS_LINUX)
94 GTEST_SKIP() << "Skipping golden tests on non-Linux OSes";
95#endif // OS_LINUX
96 const bool golden_data_matches = golden_data->equals(snapshot_data.get());
97 if (!golden_data_matches) {
98 SkFILEWStream wstream(new_golden_file_path.c_str());
99 wstream.write(snapshot_data->data(), snapshot_data->size());
100 wstream.flush();
101
102 size_t b64_size =
103 SkBase64::Encode(snapshot_data->data(), snapshot_data->size(), nullptr);
104 sk_sp<SkData> b64_data = SkData::MakeUninitialized(b64_size + 1);
105 char* b64_char = static_cast<char*>(b64_data->writable_data());
106 SkBase64::Encode(snapshot_data->data(), snapshot_data->size(), b64_char);
107 b64_char[b64_size] = 0; // make it null terminated for printing
108
109 EXPECT_TRUE(golden_data_matches)
110 << "Golden file mismatch. Please check "
111 << "the difference between " << golden_file_path << " and "
112 << new_golden_file_path << ", and replace the former "
113 << "with the latter if the difference looks good.\nS\n"
114 << "See also the base64 encoded " << new_golden_file_path << ":\n"
115 << b64_char;
116 }
117}
118
119} // namespace
120
121using PerformanceOverlayLayerTest = LayerTest;
122
123TEST_F(PerformanceOverlayLayerTest, PaintingEmptyLayerDies) {
124 const uint64_t overlay_opts = kVisualizeRasterizerStatistics;
125 auto layer = std::make_shared<PerformanceOverlayLayer>(overlay_opts);
126
127 layer->Preroll(preroll_context(), SkMatrix());
128 EXPECT_EQ(layer->paint_bounds(), SkRect::MakeEmpty());
129 EXPECT_FALSE(layer->needs_painting());
130
131 // Crashes reading a nullptr.
132 EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), "");
133}
134
135TEST_F(PerformanceOverlayLayerTest, InvalidOptions) {
136 const SkRect layer_bounds = SkRect::MakeLTRB(0.0f, 0.0f, 64.0f, 64.0f);
137 const uint64_t overlay_opts = 0;
138 auto layer = std::make_shared<PerformanceOverlayLayer>(overlay_opts);
139
140 // TODO(): Note calling code has to call set_paint_bounds right now. Make
141 // this a constructor parameter and move the set_paint_bounds into Preroll
142 layer->set_paint_bounds(layer_bounds);
143
144 layer->Preroll(preroll_context(), SkMatrix());
145 EXPECT_EQ(layer->paint_bounds(), layer_bounds);
146 EXPECT_TRUE(layer->needs_painting());
147
148 // Nothing is drawn if options are invalid (0).
149 layer->Paint(paint_context());
150 EXPECT_EQ(mock_canvas().draw_calls(), std::vector<MockCanvas::DrawCall>());
151}
152
153TEST_F(PerformanceOverlayLayerTest, SimpleRasterizerStatistics) {
154 const SkRect layer_bounds = SkRect::MakeLTRB(0.0f, 0.0f, 64.0f, 64.0f);
155 const uint64_t overlay_opts = kDisplayRasterizerStatistics;
156 auto layer = std::make_shared<PerformanceOverlayLayer>(overlay_opts);
157
158 // TODO(): Note calling code has to call set_paint_bounds right now. Make
159 // this a constructor parameter and move the set_paint_bounds into Preroll
160 layer->set_paint_bounds(layer_bounds);
161
162 layer->Preroll(preroll_context(), SkMatrix());
163 EXPECT_EQ(layer->paint_bounds(), layer_bounds);
164 EXPECT_TRUE(layer->needs_painting());
165
166 layer->Paint(paint_context());
167 auto overlay_text = PerformanceOverlayLayer::MakeStatisticsText(
168 paint_context().raster_time, "Raster", "");
169 auto overlay_text_data = overlay_text->serialize(SkSerialProcs{});
170 SkPaint text_paint;
171 text_paint.setColor(SK_ColorGRAY);
172 SkPoint text_position = SkPoint::Make(16.0f, 22.0f);
173 EXPECT_EQ(mock_canvas().draw_calls(),
174 std::vector({MockCanvas::DrawCall{
175 0, MockCanvas::DrawTextData{overlay_text_data, text_paint,
176 text_position}}}));
177}
178
179TEST(PerformanceOverlayLayerDefault, Gold) {
180 TestPerformanceOverlayLayerGold(60);
181}
182
183TEST(PerformanceOverlayLayer90fps, Gold) {
184 TestPerformanceOverlayLayerGold(90);
185}
186
187TEST(PerformanceOverlayLayer120fps, Gold) {
188 TestPerformanceOverlayLayerGold(120);
189}
190
191} // namespace testing
192} // namespace flutter
193