Skip to content

Commit

Permalink
Support non-60 refresh rate on PerformanceOverlay (flutter#11419)
Browse files Browse the repository at this point in the history
So we can get the correct graph on 90fps/120fps devices :)

See flutter/flutter#37888
  • Loading branch information
liyuqian authored Aug 24, 2019
1 parent a1afc11 commit 4d83ef8
Show file tree
Hide file tree
Showing 13 changed files with 97 additions and 32 deletions.
3 changes: 2 additions & 1 deletion flow/compositor_context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@

namespace flutter {

CompositorContext::CompositorContext() = default;
CompositorContext::CompositorContext(fml::Milliseconds frame_budget)
: raster_time_(frame_budget), ui_time_(frame_budget) {}

CompositorContext::~CompositorContext() = default;

Expand Down
2 changes: 1 addition & 1 deletion flow/compositor_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ class CompositorContext {
FML_DISALLOW_COPY_AND_ASSIGN(ScopedFrame);
};

CompositorContext();
CompositorContext(fml::Milliseconds frame_budget = fml::kDefaultFrameBudget);

virtual ~CompositorContext();

Expand Down
25 changes: 15 additions & 10 deletions flow/instrumentation.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ namespace flutter {
static const size_t kMaxSamples = 120;
static const size_t kMaxFrameMarkers = 8;

Stopwatch::Stopwatch() : start_(fml::TimePoint::Now()), current_sample_(0) {
Stopwatch::Stopwatch(fml::Milliseconds frame_budget)
: 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;
frame_budget_ = frame_budget;
}

Stopwatch::~Stopwatch() = default;
Expand All @@ -42,12 +44,12 @@ const fml::TimeDelta& Stopwatch::LastLap() const {
return laps_[(current_sample_ - 1) % kMaxSamples];
}

static inline constexpr double UnitFrameInterval(double raster_time_ms) {
return raster_time_ms * 60.0 * 1e-3;
double Stopwatch::UnitFrameInterval(double raster_time_ms) const {
return raster_time_ms / frame_budget_.count();
}

static inline double UnitHeight(double raster_time_ms,
double max_unit_interval) {
double Stopwatch::UnitHeight(double raster_time_ms,
double max_unit_interval) const {
double unitHeight = UnitFrameInterval(raster_time_ms) / max_unit_interval;
if (unitHeight > 1.0)
unitHeight = 1.0;
Expand Down Expand Up @@ -97,7 +99,8 @@ void Stopwatch::InitVisualizeSurface(const SkRect& rect) const {

// Scale the graph to show frame times up to those that are 3 times the frame
// time.
const double max_interval = kOneFrameMS * 3.0;
const double one_frame_ms = frame_budget_.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.
Expand Down Expand Up @@ -146,7 +149,8 @@ void Stopwatch::Visualize(SkCanvas& canvas, const SkRect& rect) const {

// Scale the graph to show frame times up to those that are 3 times the frame
// time.
const double max_interval = kOneFrameMS * 3.0;
const double one_frame_ms = frame_budget_.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);
Expand Down Expand Up @@ -179,9 +183,10 @@ void Stopwatch::Visualize(SkCanvas& canvas, const SkRect& rect) const {
paint.setStyle(SkPaint::Style::kStroke_Style);
paint.setColor(0xCC000000);

if (max_interval > kOneFrameMS) {
if (max_interval > one_frame_ms) {
// Paint the horizontal markers
size_t frame_marker_count = static_cast<size_t>(max_interval / kOneFrameMS);
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
Expand All @@ -191,7 +196,7 @@ void Stopwatch::Visualize(SkCanvas& canvas, const SkRect& rect) const {
for (size_t frame_index = 0; frame_index < frame_marker_count;
frame_index++) {
const double frame_height =
height * (1.0 - (UnitFrameInterval((frame_index + 1) * kOneFrameMS) /
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);
Expand Down
12 changes: 7 additions & 5 deletions flow/instrumentation.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,9 @@

namespace flutter {

// DEPRECATED
// The frame per second FPS could be different than 60 (e.g., 120).
static const double kOneFrameMS = 1e3 / 60.0;

class Stopwatch {
public:
Stopwatch();
Stopwatch(fml::Milliseconds frame_budget = fml::kDefaultFrameBudget);

~Stopwatch();

Expand All @@ -43,9 +39,15 @@ class Stopwatch {
void SetLapTime(const fml::TimeDelta& delta);

private:
inline double UnitFrameInterval(double time_ms) const;
inline double UnitHeight(double time_ms, double max_height) const;

fml::TimePoint start_;
std::vector<fml::TimeDelta> laps_;
size_t current_sample_;

fml::Milliseconds frame_budget_;

// Mutable data cache for performance optimization of the graphs. Prevents
// expensive redrawing of old data.
mutable bool cache_dirty_;
Expand Down
44 changes: 30 additions & 14 deletions flow/layers/performance_overlay_layer_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

#include "gtest/gtest.h"

#include <sstream>

// To get the size of kMockedTimes in compile time.
template <class T, std::size_t N>
constexpr int size(const T (&array)[N]) noexcept {
Expand All @@ -22,20 +24,22 @@ 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};

// Relative to the flutter/src/engine/flutter directory
const char* kGoldenFileName = "performance_overlay_gold.png";

// Relative to the flutter/src/engine/flutter directory
const char* kNewGoldenFileName = "performance_overlay_gold_new.png";

TEST(PerformanceOverlayLayer, Gold) {
const std::string& golden_dir = flutter::GetGoldenDir();
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. "\".
std::string golden_file_path = golden_dir + "/" + kGoldenFileName;
std::string new_golden_file_path = golden_dir + "/" + kNewGoldenFileName;
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);

flutter::Stopwatch mock_stopwatch;
flutter::Stopwatch mock_stopwatch(
fml::RefreshRateToFrameBudget(refresh_rate));
for (int i = 0; i < size(kMockedTimes); ++i) {
mock_stopwatch.SetLapTime(
fml::TimeDelta::FromMilliseconds(kMockedTimes[i]));
Expand Down Expand Up @@ -91,10 +95,22 @@ TEST(PerformanceOverlayLayer, Gold) {

EXPECT_TRUE(golden_data_matches)
<< "Golden file mismatch. Please check "
<< "the difference between " << kGoldenFileName << " and "
<< kNewGoldenFileName << ", and replace the former "
<< "the difference between " << golden_file_path << " and "
<< new_golden_file_path << ", and replace the former "
<< "with the latter if the difference looks good.\n\n"
<< "See also the base64 encoded " << kNewGoldenFileName << ":\n"
<< "See also the base64 encoded " << new_golden_file_path << ":\n"
<< b64_char;
}
}

TEST(PerformanceOverlayLayerDefault, Gold) {
TestPerformanceOverlayLayerGold(60);
}

TEST(PerformanceOverlayLayer90fps, Gold) {
TestPerformanceOverlayLayerGold(90);
}

TEST(PerformanceOverlayLayer120fps, Gold) {
TestPerformanceOverlayLayerGold(120);
}
13 changes: 13 additions & 0 deletions fml/time/time_delta.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,24 @@
#include <stdint.h>
#include <time.h>

#include <chrono>
#include <iosfwd>
#include <limits>

namespace fml {

using namespace std::chrono_literals;

using Milliseconds = std::chrono::duration<double, std::milli>;

// Default to 60fps.
constexpr Milliseconds kDefaultFrameBudget = Milliseconds(1s) / 60;

template <typename T>
Milliseconds RefreshRateToFrameBudget(T refresh_rate) {
return Milliseconds(1s) / refresh_rate;
}

// A TimeDelta represents the difference between two time points.
class TimeDelta {
public:
Expand Down
3 changes: 2 additions & 1 deletion shell/common/rasterizer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ Rasterizer::Rasterizer(
Rasterizer::Rasterizer(Delegate& delegate, TaskRunners task_runners)
: Rasterizer(delegate,
std::move(task_runners),
std::make_unique<flutter::CompositorContext>()) {}
std::make_unique<flutter::CompositorContext>(
delegate.GetFrameBudget())) {}

Rasterizer::Rasterizer(
Delegate& delegate,
Expand Down
6 changes: 6 additions & 0 deletions shell/common/rasterizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,17 @@ class Rasterizer final {
/// the frame workload.
///
virtual void OnFrameRasterized(const FrameTiming& frame_timing) = 0;

/// Time limit for a smooth frame. See `Engine::GetDisplayRefreshRate`.
virtual fml::Milliseconds GetFrameBudget() = 0;
};

// TODO(dnfield): remove once embedders have caught up.
class DummyDelegate : public Delegate {
void OnFrameRasterized(const FrameTiming&) override {}
fml::Milliseconds GetFrameBudget() override {
return fml::kDefaultFrameBudget;
}
};

//----------------------------------------------------------------------------
Expand Down
11 changes: 11 additions & 0 deletions shell/common/shell.cc
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,9 @@ bool Shell::Setup(std::unique_ptr<PlatformView> platform_view,
PersistentCache::GetCacheForProcess()->SetIsDumpingSkp(
settings_.dump_skp_on_shader_compilation);

// Shell::Setup is running on the UI thread so we can do the following.
display_refresh_rate_ = weak_engine_->GetDisplayRefreshRate();

return true;
}

Expand Down Expand Up @@ -1070,6 +1073,14 @@ void Shell::OnFrameRasterized(const FrameTiming& timing) {
}
}

fml::Milliseconds Shell::GetFrameBudget() {
if (display_refresh_rate_ > 0) {
return fml::RefreshRateToFrameBudget(display_refresh_rate_.load());
} else {
return fml::kDefaultFrameBudget;
}
}

// |ServiceProtocol::Handler|
fml::RefPtr<fml::TaskRunner> Shell::GetServiceProtocolHandlerTaskRunner(
std::string_view method) const {
Expand Down
10 changes: 10 additions & 0 deletions shell/common/shell.h
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,13 @@ class Shell final : public PlatformView::Delegate,
// here for easier conversions to Dart objects.
std::vector<int64_t> unreported_timings_;

// A cache of `Engine::GetDisplayRefreshRate` (only callable in the UI thread)
// so we can access it from `Rasterizer` (in the GPU thread).
//
// The atomic is for extra thread safety as this is written in the UI thread
// and read from the GPU thread.
std::atomic<float> display_refresh_rate_ = 0.0f;

// How many frames have been timed since last report.
size_t UnreportedFramesCount() const;

Expand Down Expand Up @@ -451,6 +458,9 @@ class Shell final : public PlatformView::Delegate,
// |Rasterizer::Delegate|
void OnFrameRasterized(const FrameTiming&) override;

// |Rasterizer::Delegate|
fml::Milliseconds GetFrameBudget() override;

// |ServiceProtocol::Handler|
fml::RefPtr<fml::TaskRunner> GetServiceProtocolHandlerTaskRunner(
std::string_view method) const override;
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 4d83ef8

Please sign in to comment.