blob: fa59d170ed8a61ca8521ab834a8373bfc126f9f7 [file] [log] [blame]
// 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/layers/performance_overlay_layer.h"
#include <cstdint>
#include <sstream>
#include "flutter/display_list/utils/dl_receiver_utils.h"
#include "flutter/flow/flow_test_utils.h"
#include "flutter/flow/raster_cache.h"
#include "flutter/flow/testing/layer_test.h"
#include "flutter/shell/common/base64.h"
#include "third_party/skia/include/core/SkData.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkSerialProcs.h"
#include "third_party/skia/include/core/SkStream.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/skia/include/core/SkTextBlob.h"
#include "third_party/skia/include/encode/SkPngEncoder.h"
namespace flutter {
namespace testing {
namespace {
// To get the size of kMockedTimes in compile time.
template <class T, std::size_t N>
constexpr int size(const T (&array)[N]) noexcept {
return N;
}
constexpr int kMockedTimes[] = {17, 1, 4, 24, 4, 25, 30, 4, 13, 34,
14, 0, 18, 9, 32, 36, 26, 23, 5, 8,
32, 18, 29, 16, 29, 18, 0, 36, 33, 10};
static std::string GetGoldenFilePath(int refresh_rate, bool is_new) {
std::stringstream ss;
// This unit test should only be run on Linux (not even on Mac since it's a
// golden test). Hence we don't have to worry about the "/" vs. "\".
ss << flutter::GetGoldenDir() << "/" << "performance_overlay_gold_"
<< refresh_rate << "fps" << (is_new ? "_new" : "") << ".png";
return ss.str();
}
static void TestPerformanceOverlayLayerGold(int refresh_rate) {
std::string golden_file_path = GetGoldenFilePath(refresh_rate, false);
std::string new_golden_file_path = GetGoldenFilePath(refresh_rate, true);
FixedRefreshRateStopwatch mock_stopwatch(
fml::RefreshRateToFrameBudget(refresh_rate));
for (int i = 0; i < size(kMockedTimes); ++i) {
mock_stopwatch.SetLapTime(
fml::TimeDelta::FromMilliseconds(kMockedTimes[i]));
}
const SkImageInfo image_info = SkImageInfo::MakeN32Premul(1000, 1000);
sk_sp<SkSurface> surface = SkSurfaces::Raster(image_info);
DlSkCanvasAdapter canvas(surface->getCanvas());
ASSERT_TRUE(surface != nullptr);
LayerStateStack state_stack;
state_stack.set_delegate(&canvas);
flutter::PaintContext paint_context = {
// clang-format off
.state_stack = state_stack,
.canvas = &canvas,
.gr_context = nullptr,
.view_embedder = nullptr,
.raster_time = mock_stopwatch,
.ui_time = mock_stopwatch,
.texture_registry = nullptr,
.raster_cache = nullptr,
// clang-format on
};
// Specify font file to ensure the same font across different operation
// systems.
flutter::PerformanceOverlayLayer layer(
flutter::kDisplayRasterizerStatistics |
flutter::kVisualizeRasterizerStatistics |
flutter::kDisplayEngineStatistics |
flutter::kVisualizeEngineStatistics,
flutter::GetFontFile().c_str());
layer.set_paint_bounds(SkRect::MakeWH(1000, 400));
surface->getCanvas()->clear(SK_ColorTRANSPARENT);
layer.Paint(paint_context);
sk_sp<SkImage> snapshot = surface->makeImageSnapshot();
sk_sp<SkData> snapshot_data =
SkPngEncoder::Encode(nullptr, snapshot.get(), {});
sk_sp<SkData> golden_data =
SkData::MakeFromFileName(golden_file_path.c_str());
EXPECT_TRUE(golden_data != nullptr)
<< "Golden file not found: " << golden_file_path << ".\n"
<< "Please either set --golden-dir, or make sure that the unit test is "
<< "run from the right directory (e.g., flutter/engine/src).";
// TODO(https://github.com/flutter/flutter/issues/53784): enable this on all
// platforms.
#if !defined(FML_OS_LINUX)
GTEST_SKIP() << "Skipping golden tests on non-Linux OSes";
#else
const bool golden_data_matches = golden_data->equals(snapshot_data.get());
if (!golden_data_matches) {
SkFILEWStream wstream(new_golden_file_path.c_str());
wstream.write(snapshot_data->data(), snapshot_data->size());
wstream.flush();
size_t b64_size = Base64::EncodedSize(snapshot_data->size());
sk_sp<SkData> b64_data = SkData::MakeUninitialized(b64_size + 1);
char* b64_char = static_cast<char*>(b64_data->writable_data());
Base64::Encode(snapshot_data->data(), snapshot_data->size(), b64_char);
b64_char[b64_size] = 0; // make it null terminated for printing
EXPECT_TRUE(golden_data_matches)
<< "Golden file mismatch. Please check " << "the difference between "
<< golden_file_path << " and " << new_golden_file_path
<< ", and replace the former "
<< "with the latter if the difference looks good.\nS\n"
<< "See also the base64 encoded " << new_golden_file_path << ":\n"
<< b64_char;
}
#endif // FML_OS_LINUX
}
} // namespace
using PerformanceOverlayLayerTest = LayerTest;
class ImageSizeTextBlobInspector : public virtual DlOpReceiver,
virtual IgnoreAttributeDispatchHelper,
virtual IgnoreTransformDispatchHelper,
virtual IgnoreClipDispatchHelper,
virtual IgnoreDrawDispatchHelper {
public:
void drawImage(const sk_sp<DlImage> image,
const DlPoint& point,
DlImageSampling sampling,
bool render_with_attributes) override {
sizes_.push_back(image->GetBounds().GetSize());
}
void drawTextBlob(const sk_sp<SkTextBlob> blob,
DlScalar x,
DlScalar y) override {
text_blobs_.push_back(blob);
text_positions_.push_back(DlPoint(x, y));
}
const std::vector<DlISize>& sizes() { return sizes_; }
const std::vector<sk_sp<SkTextBlob>> text_blobs() { return text_blobs_; }
const std::vector<DlPoint> text_positions() { return text_positions_; }
private:
std::vector<DlISize> sizes_;
std::vector<sk_sp<SkTextBlob>> text_blobs_;
std::vector<DlPoint> text_positions_;
};
TEST_F(PerformanceOverlayLayerTest, PaintingEmptyLayerDies) {
const uint64_t overlay_opts = kVisualizeRasterizerStatistics;
auto layer = std::make_shared<PerformanceOverlayLayer>(overlay_opts);
layer->Preroll(preroll_context());
EXPECT_EQ(layer->paint_bounds(), SkRect::MakeEmpty());
EXPECT_FALSE(layer->needs_painting(paint_context()));
// Crashes reading a nullptr.
EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), "");
}
TEST_F(PerformanceOverlayLayerTest, InvalidOptions) {
const SkRect layer_bounds = SkRect::MakeLTRB(0.0f, 0.0f, 64.0f, 64.0f);
const uint64_t overlay_opts = 0;
auto layer = std::make_shared<PerformanceOverlayLayer>(overlay_opts);
// TODO(): Note calling code has to call set_paint_bounds right now. Make
// this a constructor parameter and move the set_paint_bounds into Preroll
layer->set_paint_bounds(layer_bounds);
layer->Preroll(preroll_context());
EXPECT_EQ(layer->paint_bounds(), layer_bounds);
EXPECT_TRUE(layer->needs_painting(paint_context()));
// Nothing is drawn if options are invalid (0).
layer->Paint(display_list_paint_context());
DisplayListBuilder expected_builder;
auto expected_dl = expected_builder.Build();
EXPECT_TRUE(DisplayListsEQ_Verbose(display_list(), expected_dl));
}
TEST_F(PerformanceOverlayLayerTest, SimpleRasterizerStatistics) {
const SkRect layer_bounds = SkRect::MakeLTRB(0.0f, 0.0f, 64.0f, 64.0f);
const uint64_t overlay_opts = kDisplayRasterizerStatistics;
auto layer = std::make_shared<PerformanceOverlayLayer>(overlay_opts);
// TODO(): Note calling code has to call set_paint_bounds right now. Make
// this a constructor parameter and move the set_paint_bounds into Preroll
layer->set_paint_bounds(layer_bounds);
layer->Preroll(preroll_context());
EXPECT_EQ(layer->paint_bounds(), layer_bounds);
EXPECT_TRUE(layer->needs_painting(paint_context()));
layer->Paint(display_list_paint_context());
auto overlay_text = PerformanceOverlayLayer::MakeStatisticsText(
display_list_paint_context().raster_time, "Raster", "");
auto overlay_text_data = overlay_text->serialize(SkSerialProcs{});
// Historically SK_ColorGRAY (== 0xFF888888) was used here
DlPaint text_paint(DlColor(0xFF888888));
DlPoint text_position = DlPoint(16.0f, 22.0f);
ImageSizeTextBlobInspector inspector;
display_list()->Dispatch(inspector);
// TODO(https://github.com/flutter/flutter/issues/82202): Remove once the
// performance overlay can use Fuchsia's font manager instead of the empty
// default.
#if defined(OS_FUCHSIA)
GTEST_SKIP() << "Expectation requires a valid default font manager";
#endif // OS_FUCHSIA
ASSERT_EQ(inspector.sizes().size(), 0u);
ASSERT_EQ(inspector.text_blobs().size(), 1u);
ASSERT_EQ(inspector.text_positions().size(), 1u);
auto text_data = inspector.text_blobs().front()->serialize(SkSerialProcs{});
EXPECT_TRUE(text_data->equals(overlay_text_data.get()));
EXPECT_EQ(inspector.text_positions().front(), text_position);
}
TEST_F(PerformanceOverlayLayerTest, MarkAsDirtyWhenResized) {
// Regression test for https://github.com/flutter/flutter/issues/54188
// Create a PerformanceOverlayLayer.
const uint64_t overlay_opts = kVisualizeRasterizerStatistics;
auto layer = std::make_shared<PerformanceOverlayLayer>(overlay_opts);
layer->set_paint_bounds(SkRect::MakeLTRB(0.0f, 0.0f, 48.0f, 48.0f));
layer->Preroll(preroll_context());
layer->Paint(display_list_paint_context());
DlISize first_draw_size;
{
ImageSizeTextBlobInspector inspector;
display_list()->Dispatch(inspector);
ASSERT_EQ(inspector.sizes().size(), 1u);
ASSERT_EQ(inspector.text_blobs().size(), 0u);
ASSERT_EQ(inspector.text_positions().size(), 0u);
first_draw_size = inspector.sizes().front();
}
// Create a second PerformanceOverlayLayer with different bounds.
layer = std::make_shared<PerformanceOverlayLayer>(overlay_opts);
layer->set_paint_bounds(SkRect::MakeLTRB(0.0f, 0.0f, 64.0f, 64.0f));
layer->Preroll(preroll_context());
reset_display_list();
layer->Paint(display_list_paint_context());
{
ImageSizeTextBlobInspector inspector;
display_list()->Dispatch(inspector);
ASSERT_EQ(inspector.sizes().size(), 1u);
ASSERT_EQ(inspector.text_blobs().size(), 0u);
ASSERT_EQ(inspector.text_positions().size(), 0u);
EXPECT_NE(first_draw_size, inspector.sizes().front());
}
}
TEST(PerformanceOverlayLayerDefault, Gold) {
TestPerformanceOverlayLayerGold(60);
}
TEST(PerformanceOverlayLayer90fps, Gold) {
TestPerformanceOverlayLayerGold(90);
}
TEST(PerformanceOverlayLayer120fps, Gold) {
TestPerformanceOverlayLayerGold(120);
}
} // namespace testing
} // namespace flutter