// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/hud_display/fps_graph_page_view.h"
#include <algorithm>
#include <cmath>
#include <numeric>
#include "ash/hud_display/hud_constants.h"
#include "ash/hud_display/reference_lines.h"
#include "ash/shell.h"
#include "base/functional/bind.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/compositor/compositor.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/presentation_feedback.h"
#include "ui/views/widget/widget.h"
namespace ash {
namespace hud_display {
namespace {
// Draw tick on the vertical axis every 5 frames.
constexpr float kVerticalTickFrames = 5;
} // namespace
////////////////////////////////////////////////////////////////////////////////
// FPSGraphPageView, public:
BEGIN_METADATA(FPSGraphPageView)
END_METADATA
FPSGraphPageView::FPSGraphPageView(const base::TimeDelta refresh_interval)
: frame_rate_1s_(kHUDGraphWidth,
Graph::Baseline::kBaselineBottom,
Graph::Fill::kNone,
Graph::Style::kSkyline,
SkColorSetA(SK_ColorYELLOW, kHUDAlpha)),
frame_rate_500ms_(kHUDGraphWidth,
Graph::Baseline::kBaselineBottom,
Graph::Fill::kNone,
Graph::Style::kSkyline,
SkColorSetA(SK_ColorCYAN, kHUDAlpha)),
refresh_rate_(kHUDGraphWidth,
Graph::Baseline::kBaselineBottom,
Graph::Fill::kNone,
Graph::Style::kSkyline,
kHUDBackground /*not drawn*/) {
const int data_width = frame_rate_1s_.max_data_points();
// Verical ticks are drawn every 5 frames (5/60 interval).
constexpr float vertical_ticks_interval = kVerticalTickFrames / 60.F;
// max_data_points left label, 60fps top, 0 seconds on the right, 0fps on the
// bottom. Seconds and fps are dimensions. Number of data points is
// data_width, horizontal tick marks are drawn every 10 frames.
reference_lines_ = CreateReferenceLines(
/*left=*/data_width,
/*top=*/60, /*right=*/0, /*bottom=*/0,
/*x_unit=*/u"frames",
/*y_unit=*/u"fps",
/*horizontal_points_number=*/data_width,
/*horizontal_ticks_interval=*/10, vertical_ticks_interval);
Legend::Formatter formatter_float = base::BindRepeating([](float value) {
return base::ASCIIToUTF16(base::StringPrintf("%.1f", value));
});
Legend::Formatter formatter_int = base::BindRepeating([](float value) {
return base::ASCIIToUTF16(base::StringPrintf("%d", (int)value));
});
const std::vector<Legend::Entry> legend(
{{refresh_rate_, u"Refresh rate", u"Actual display refresh rate.",
formatter_int},
{frame_rate_1s_, u"1s FPS",
u"Number of frames successfully presented per 1 second.",
formatter_float},
{frame_rate_500ms_, u".5s FPS",
u"Number of frames successfully presented per 0.5 second scaled to a "
u"second.",
formatter_float}});
CreateLegend(legend);
}
FPSGraphPageView::~FPSGraphPageView() = default;
////////////////////////////////////////////////////////////////////////////////
void FPSGraphPageView::AddedToWidget() {
GraphPageViewBase::AddedToWidget();
GetWidget()->AddObserver(this);
}
void FPSGraphPageView::RemovedFromWidget() {
GetWidget()->RemoveObserver(this);
GraphPageViewBase::RemovedFromWidget();
}
void FPSGraphPageView::OnPaint(gfx::Canvas* canvas) {
// TODO: Should probably update last graph point more often than shift graph.
// Layout graphs.
gfx::Rect rect = GetContentsBounds();
// Adjust bounds to not overlap with bordering reference lines.
rect.Inset(kHUDGraphReferenceLineWidth);
frame_rate_500ms_.Layout(rect, /*base=*/nullptr);
frame_rate_1s_.Layout(rect, /*base=*/nullptr);
frame_rate_500ms_.Draw(canvas);
frame_rate_1s_.Draw(canvas);
// Refresh rate graph is not drawn, it's just used in Legend display and
// reference line calculations.
}
void FPSGraphPageView::OnDidPresentCompositorFrame(
uint32_t frame_token,
const gfx::PresentationFeedback& feedback) {
UpdateStats(feedback);
float frame_rate_1s = frame_rate_for_last_second();
float frame_rate_500ms = frame_rate_for_last_half_second();
float refresh_rate = GetWidget()->GetCompositor()->refresh_rate();
UpdateTopLabel(refresh_rate);
frame_rate_1s_.AddValue(frame_rate_1s / reference_lines_->top_label(),
frame_rate_1s);
frame_rate_500ms_.AddValue(frame_rate_500ms / reference_lines_->top_label(),
frame_rate_500ms);
const float max_refresh_rate =
std::max(refresh_rate, refresh_rate_.GetUnscaledValueAt(0));
refresh_rate_.AddValue(max_refresh_rate / reference_lines_->top_label(),
max_refresh_rate);
// Legend update is expensive. Do it synchronously on regular intervals only.
if (GetVisible())
SchedulePaint();
}
void FPSGraphPageView::UpdateData(const DataSource::Snapshot& snapshot) {
if (!GetWidget()->GetNativeWindow()->HasObserver(this)) {
GetWidget()->GetNativeWindow()->AddObserver(this);
GetWidget()->GetCompositor()->AddObserver(this);
}
// Graph moves only on FramePresented.
// Update legend only.
RefreshLegendValues();
}
void FPSGraphPageView::OnWidgetDestroying(views::Widget* widget) {
DCHECK_EQ(widget, GetWidget());
// Remove observe for destruction.
GetWidget()->GetNativeWindow()->RemoveObserver(this);
GetWidget()->GetCompositor()->RemoveObserver(this);
}
void FPSGraphPageView::OnWindowAddedToRootWindow(aura::Window* window) {
GetWidget()->GetCompositor()->AddObserver(this);
}
void FPSGraphPageView::OnWindowRemovingFromRootWindow(aura::Window* window,
aura::Window* new_root) {
if (GetWidget() && GetWidget()->GetCompositor() &&
GetWidget()->GetCompositor()->HasObserver(this)) {
GetWidget()->GetCompositor()->RemoveObserver(this);
}
}
void FPSGraphPageView::UpdateStats(const gfx::PresentationFeedback& feedback) {
constexpr base::TimeDelta kOneSec = base::Seconds(1);
constexpr base::TimeDelta k500ms = base::Milliseconds(500);
if (!feedback.failed())
presented_times_.push_back(feedback.timestamp);
const base::TimeTicks deadline_1s = feedback.timestamp - kOneSec;
while (!presented_times_.empty() && presented_times_.front() <= deadline_1s)
presented_times_.pop_front();
const base::TimeTicks deadline_500ms = feedback.timestamp - k500ms;
frame_rate_for_last_half_second_ = 0;
for (auto i = presented_times_.crbegin();
(i != presented_times_.crend()) && (*i > deadline_500ms); ++i) {
++frame_rate_for_last_half_second_;
}
frame_rate_for_last_half_second_ *= 2;
}
void FPSGraphPageView::UpdateTopLabel(float refresh_rate) {
const float refresh_rate_rounded_10 =
ceilf(unsigned(refresh_rate) * 0.1F) * 10.F;
if (reference_lines_->top_label() != refresh_rate_rounded_10) {
frame_rate_1s_.Reset();
frame_rate_500ms_.Reset();
reference_lines_->SetTopLabel(refresh_rate_rounded_10);
reference_lines_->SetVerticalTicksInterval(kVerticalTickFrames /
refresh_rate_rounded_10);
}
}
} // namespace hud_display
} // namespace ash