blob: 2f21e2fcbfca2e5de8cfb2bf627cf45afb23373b [file] [log] [blame]
// Copyright 2021 The Chromium 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 "components/power_scheduler/power_mode_voter.h"
#include <limits>
#include "base/command_line.h"
#include "base/metrics/histogram_macros.h"
#include "components/power_scheduler/power_mode_arbiter.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/vector2d.h"
namespace power_scheduler {
namespace {
bool IsBrowserProcess() {
return base::CommandLine::ForCurrentProcess()
->GetSwitchValueASCII("type")
.empty();
}
const char* GetFrameDamageAreaHistogramName() {
return IsBrowserProcess() ? "Power.FrameDamageAreaInDip.Browser"
: "Power.FrameDamageAreaInDip.Renderer";
}
const char* GetFrameDamageDiagonalHistogramName() {
return IsBrowserProcess() ? "Power.FrameDamageDiagonalInDip.Browser"
: "Power.FrameDamageDiagonalInDip.Renderer";
}
} // namespace
PowerModeVoter::Delegate::~Delegate() = default;
PowerModeVoter::PowerModeVoter(Delegate* delegate) : delegate_(delegate) {}
// static
constexpr base::TimeDelta PowerModeVoter::kResponseTimeout;
// static
constexpr base::TimeDelta PowerModeVoter::kAnimationTimeout;
// static
constexpr base::TimeDelta PowerModeVoter::kVideoTimeout;
// static
constexpr base::TimeDelta PowerModeVoter::kSoftwareDrawTimeout;
// static
constexpr base::TimeDelta PowerModeVoter::kLoadingTimeout;
// static
constexpr base::TimeDelta PowerModeVoter::kStuckLoadingTimeout;
PowerModeVoter::~PowerModeVoter() {
delegate_->OnVoterDestroyed(this);
}
void PowerModeVoter::VoteFor(PowerMode mode) {
delegate_->SetVote(this, mode);
}
void PowerModeVoter::ResetVoteAfterTimeout(base::TimeDelta timeout) {
delegate_->ResetVoteAfterTimeout(this, timeout);
}
// static
constexpr int FrameProductionPowerModeVoter::kMinFramesSkippedForIdleAnimation;
FrameProductionPowerModeVoter::FrameProductionPowerModeVoter(const char* name)
: FrameProductionPowerModeVoter(name, PowerModeArbiter::GetInstance()) {}
FrameProductionPowerModeVoter::FrameProductionPowerModeVoter(
const char* name,
PowerModeArbiter* arbiter)
: voter_(arbiter->NewVoter(name)) {}
FrameProductionPowerModeVoter::~FrameProductionPowerModeVoter() = default;
void FrameProductionPowerModeVoter::OnNeedsBeginFramesChanged(
bool needs_begin_frames) {
needs_begin_frames_ = needs_begin_frames;
if (needs_begin_frames) {
in_nop_animation_ = false;
consecutive_frames_skipped_ = 0;
// Start out with a 2 x max frame damage history. Thus, we will require
// |kNumDamageAreas-1| frames with lower damage area before voting for
// small/medium animation modes.
last_damage_areas_ = {std::numeric_limits<int>::max(),
std::numeric_limits<int>::max()};
voter_->VoteFor(PowerMode::kAnimation);
} else {
voter_->ResetVoteAfterTimeout(PowerModeVoter::kAnimationTimeout);
}
}
void FrameProductionPowerModeVoter::OnFrameProduced(
const gfx::Rect& damage_rect,
float device_scale_factor) {
// Only consider produced frames when we still need BeginFrames.
if (!needs_begin_frames_)
return;
// In DIP. Assuming 160 dip/inch, one square inch is 160*160 = 25600 dip. A
// typical 6 inch screen has about 13.5 square inch area. We'll define
// animations up to 3 square inch as "small" animation and 6 as "medium".
static constexpr int kMaxDamageAreaForSmallAnimation = 3 * 160 * 160;
static constexpr int kMaxDamageAreaForMediumAnimation = 6 * 160 * 160;
PowerMode vote = PowerMode::kAnimation;
gfx::Size damage_rect_in_dip;
if (device_scale_factor > 0) {
float inverse_dsf = 1 / device_scale_factor;
damage_rect_in_dip =
gfx::ScaleToCeiledSize(damage_rect.size(), inverse_dsf);
}
if (!damage_rect_in_dip.IsEmpty()) {
int damage_area_in_dip = damage_rect_in_dip.GetCheckedArea().ValueOrDefault(
std::numeric_limits<int>::max());
int damage_diagonal_in_dip = std::ceil(
gfx::Vector2d(damage_rect_in_dip.width(), damage_rect_in_dip.height())
.Length());
UMA_HISTOGRAM_COUNTS_10M(GetFrameDamageAreaHistogramName(),
damage_area_in_dip);
UMA_HISTOGRAM_COUNTS_10000(GetFrameDamageDiagonalHistogramName(),
damage_diagonal_in_dip);
// Record the area into last_damage_areas_ after shifting existing values.
for (int i = kNumDamageAreas - 1; i >= 1; i--)
last_damage_areas_[i] = last_damage_areas_[i - 1];
last_damage_areas_[0] = damage_area_in_dip;
// Determine the second largest area damaged over the last kNumDamageAreas
// frames, ignoring the maximum. This way, we ignore one-off frames that
// damage a large area but are submitted in between a sequence of smaller
// animation frames.
int max_damage_area = 0;
int second_max_damage_area = 0;
for (int damage_area : last_damage_areas_) {
if (damage_area > max_damage_area) {
second_max_damage_area = max_damage_area;
max_damage_area = damage_area;
} else if (damage_area > second_max_damage_area) {
second_max_damage_area = damage_area;
}
}
if (second_max_damage_area <= kMaxDamageAreaForSmallAnimation) {
vote = PowerMode::kSmallAnimation;
} else if (second_max_damage_area <= kMaxDamageAreaForMediumAnimation) {
vote = PowerMode::kMediumAnimation;
}
}
consecutive_frames_skipped_ = 0;
// If we were in no-op mode, only go back into an animation mode if there were
// at least two frames produced within kAnimationTimeout.
base::TimeTicks now = base::TimeTicks::Now();
if (in_nop_animation_ &&
last_frame_produced_timestamp_ + PowerModeVoter::kAnimationTimeout <
now) {
last_frame_produced_timestamp_ = now;
return;
}
in_nop_animation_ = false;
last_frame_produced_timestamp_ = now;
voter_->VoteFor(vote);
if (!needs_begin_frames_)
voter_->ResetVoteAfterTimeout(PowerModeVoter::kAnimationTimeout);
}
void FrameProductionPowerModeVoter::OnFrameSkipped(bool frame_completed,
bool waiting_on_main) {
// Only consider skipped frames when we still need BeginFrames.
if (!needs_begin_frames_)
return;
// Ignore frames that are skipped in an incomplete state, e.g. because frame
// production took too long and the deadline was missed. Such frames should
// not count as "no-op", because frame production may still be in progress.
// However, if we were only waiting on the main thread, we will treat this as
// no-op here, because we cannot distinguish aborted BeginMainFrame sequences
// from "long" content-producing BeginMainFrames here. Instead, a separate
// PowerModeVoter tracks BeginMainFrame production in cc::Scheduler.
if (!frame_completed && !waiting_on_main)
return;
if (consecutive_frames_skipped_ < kMinFramesSkippedForIdleAnimation - 1) {
consecutive_frames_skipped_++;
return;
}
in_nop_animation_ = true;
last_frame_produced_timestamp_ = base::TimeTicks();
voter_->VoteFor(PowerMode::kNopAnimation);
}
void FrameProductionPowerModeVoter::OnFrameTimeout() {
in_nop_animation_ = true;
voter_->VoteFor(PowerMode::kNopAnimation);
}
DebouncedPowerModeVoter::DebouncedPowerModeVoter(const char* name,
base::TimeDelta timeout)
: voter_(PowerModeArbiter::GetInstance()->NewVoter(name)),
timeout_(timeout) {}
DebouncedPowerModeVoter::~DebouncedPowerModeVoter() = default;
void DebouncedPowerModeVoter::VoteFor(PowerMode vote) {
base::TimeTicks now = base::TimeTicks::Now();
if (!last_vote_ || vote != *last_vote_ ||
last_vote_timestamp_ + timeout_ < now) {
last_vote_ = vote;
last_vote_timestamp_ = now;
return;
}
DCHECK_EQ(*last_vote_, vote);
last_vote_timestamp_ = now;
voter_->VoteFor(vote);
}
} // namespace power_scheduler