blob: 624ca8f32e12d9cc6c1052cd23f1a452d8b69412 [file] [log] [blame]
// Copyright 2019 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/viz/service/display/frame_rate_decider.h"
#include <algorithm>
#include <utility>
#include "components/viz/common/frame_sinks/begin_frame_args.h"
#include "components/viz/service/surfaces/surface.h"
#include "components/viz/service/surfaces/surface_manager.h"
namespace viz {
namespace {
bool AreAlmostEqual(base::TimeDelta a, base::TimeDelta b) {
if (a.is_min() || b.is_min() || a.is_max() || b.is_max())
return a == b;
constexpr auto kMaxDelta = base::TimeDelta::FromMillisecondsD(0.5);
return (a - b).magnitude() < kMaxDelta;
}
} // namespace
FrameRateDecider::ScopedAggregate::ScopedAggregate(FrameRateDecider* decider)
: decider_(decider) {
decider_->StartAggregation();
}
FrameRateDecider::ScopedAggregate::~ScopedAggregate() {
decider_->EndAggregation();
}
FrameRateDecider::FrameRateDecider(SurfaceManager* surface_manager,
Client* client,
bool hw_support_for_multiple_refresh_rates,
bool supports_set_frame_rate,
size_t num_of_frames_to_toggle_interval)
: supported_intervals_{BeginFrameArgs::DefaultInterval()},
min_num_of_frames_to_toggle_interval_(num_of_frames_to_toggle_interval),
surface_manager_(surface_manager),
client_(client),
hw_support_for_multiple_refresh_rates_(
hw_support_for_multiple_refresh_rates),
supports_set_frame_rate_(supports_set_frame_rate) {
// For sources which have no preference, allow lowering them to up to
// 24Hz.
double interval_in_seconds = 1.0 / 24.0;
frame_interval_for_sinks_with_no_preference_ =
base::TimeDelta::FromSecondsD(interval_in_seconds);
surface_manager_->AddObserver(this);
}
FrameRateDecider::~FrameRateDecider() {
surface_manager_->RemoveObserver(this);
}
void FrameRateDecider::SetSupportedFrameIntervals(
std::vector<base::TimeDelta> supported_intervals) {
DCHECK(!inside_surface_aggregation_);
supported_intervals_ = std::move(supported_intervals);
std::sort(supported_intervals_.begin(), supported_intervals_.end());
UpdatePreferredFrameIntervalIfNeeded();
}
void FrameRateDecider::OnSurfaceWillBeDrawn(Surface* surface) {
// If there are multiple displays, we receive callbacks when a surface is
// drawn on any of these displays. Ensure that we only update the internal
// tracking when the Display corresponding to this decider is drawing.
if (!inside_surface_aggregation_)
return;
if (!multiple_refresh_rates_supported())
return;
// Update the list of surfaces drawn in this frame along with the currently
// active CompositorFrame. We use the list from the previous frame to track
// which surfaces were updated in this display draw.
const SurfaceId& surface_id = surface->surface_id();
const uint64_t active_index = surface->GetActiveFrameIndex();
auto it = current_surface_id_to_active_index_.find(surface_id);
if (it == current_surface_id_to_active_index_.end()) {
current_surface_id_to_active_index_[surface_id] = active_index;
} else {
DCHECK_EQ(it->second, active_index)
<< "Same display frame should not draw a surface with different "
"CompositorFrames";
}
it = prev_surface_id_to_active_index_.find(surface_id);
if (it == prev_surface_id_to_active_index_.end() ||
it->second != active_index) {
frame_sinks_updated_in_previous_frame_.insert(surface_id.frame_sink_id());
}
frame_sinks_drawn_in_previous_frame_.insert(surface_id.frame_sink_id());
}
void FrameRateDecider::StartAggregation() {
DCHECK(!inside_surface_aggregation_);
inside_surface_aggregation_ = true;
frame_sinks_updated_in_previous_frame_.clear();
frame_sinks_drawn_in_previous_frame_.clear();
}
void FrameRateDecider::EndAggregation() {
DCHECK(inside_surface_aggregation_);
inside_surface_aggregation_ = false;
prev_surface_id_to_active_index_.swap(current_surface_id_to_active_index_);
current_surface_id_to_active_index_.clear();
UpdatePreferredFrameIntervalIfNeeded();
}
void FrameRateDecider::UpdatePreferredFrameIntervalIfNeeded() {
if (!multiple_refresh_rates_supported())
return;
int num_of_frame_sinks_with_fixed_interval = 0;
int num_of_frame_sinks_with_no_preference = 0;
for (const auto& frame_sink_id : frame_sinks_drawn_in_previous_frame_) {
auto type = mojom::CompositorFrameSinkType::kUnspecified;
auto interval =
client_->GetPreferredFrameIntervalForFrameSinkId(frame_sink_id, &type);
switch (type) {
case mojom::CompositorFrameSinkType::kUnspecified:
DCHECK_EQ(interval, BeginFrameArgs::MinInterval());
continue;
case mojom::CompositorFrameSinkType::kVideo:
num_of_frame_sinks_with_fixed_interval++;
break;
case mojom::CompositorFrameSinkType::kMediaStream:
num_of_frame_sinks_with_fixed_interval++;
break;
case mojom::CompositorFrameSinkType::kLayerTree:
if (interval == BeginFrameArgs::MaxInterval()) {
num_of_frame_sinks_with_no_preference++;
}
break;
}
}
if (!ShouldToggleFrameInterval(num_of_frame_sinks_with_fixed_interval,
num_of_frame_sinks_with_no_preference)) {
TRACE_EVENT_INSTANT0(
"viz",
"FrameRateDecider::UpdatePreferredFrameIntervalIfNeeded - not enough "
"frame sinks to toggle",
TRACE_EVENT_SCOPE_THREAD);
SetPreferredInterval(UnspecifiedFrameInterval());
return;
}
// The code below picks the optimal frame interval for the display based on
// the frame sinks which were updated in this frame. This is because we want
// the display's update rate to be decided based on onscreen content that is
// animating. This ensures that, for instance, if we're currently displaying
// a video while the rest of the page is static, we choose the frame interval
// optimal for the video.
base::Optional<base::TimeDelta> min_frame_sink_interval;
bool all_frame_sinks_have_same_interval = true;
for (const auto& frame_sink_id : frame_sinks_updated_in_previous_frame_) {
auto interval =
client_->GetPreferredFrameIntervalForFrameSinkId(frame_sink_id);
if (interval == BeginFrameArgs::MaxInterval()) {
interval = frame_interval_for_sinks_with_no_preference_;
}
if (!min_frame_sink_interval) {
min_frame_sink_interval = interval;
continue;
}
if (!AreAlmostEqual(*min_frame_sink_interval, interval))
all_frame_sinks_have_same_interval = false;
min_frame_sink_interval = std::min(*min_frame_sink_interval, interval);
}
// A redraw was done with no onscreen content getting updated, avoid updating
// the interval in this case.
if (!min_frame_sink_interval) {
return;
}
TRACE_EVENT_INSTANT1("viz",
"FrameRateDecider::UpdatePreferredFrameIntervalIfNeeded",
TRACE_EVENT_SCOPE_THREAD, "min_frame_sink_interval",
min_frame_sink_interval->InMillisecondsF());
// If only one frame sink is being updated and its frame rate can be directly
// forwarded to the system, then prefer that over choosing one of the refresh
// rates advertised by the system.
if (all_frame_sinks_have_same_interval && supports_set_frame_rate_) {
SetPreferredInterval(*min_frame_sink_interval);
return;
}
// If we don't have an explicit preference from the active frame sinks, then
// we use a 0 value for preferred frame interval to let the framework pick the
// ideal refresh rate.
base::TimeDelta new_preferred_interval = UnspecifiedFrameInterval();
if (*min_frame_sink_interval != BeginFrameArgs::MinInterval()) {
for (auto supported_interval : supported_intervals_) {
// Pick the display interval which is closest to the preferred interval.
if ((*min_frame_sink_interval - supported_interval).magnitude() <
(*min_frame_sink_interval - new_preferred_interval).magnitude()) {
new_preferred_interval = supported_interval;
}
}
}
SetPreferredInterval(new_preferred_interval);
}
bool FrameRateDecider::ShouldToggleFrameInterval(
int num_of_frame_sinks_with_fixed_interval,
int num_of_frame_sinks_with_no_preference) const {
// If there is no fixed rate content, we don't try to lower the frame rate.
if (num_of_frame_sinks_with_fixed_interval == 0)
return false;
// If lowering the refresh rate is supported by the platform then we try to
// do this in all cases where any content drawing onscreen animates at a
// fixed rate. This includes surfaces backed by videos or media streams.
if (hw_support_for_multiple_refresh_rates_)
return num_of_frame_sinks_with_fixed_interval > 0;
// If we're reducing frame rate for the display compositor, as opposed to the
// underlying platform compositor or physical display, then restrict it to
// cases with multiple animating sources that can be lowered. We should be
// able to do it for all video cases but this results in dropped frame
// regressions which need to be investigated (see crbug.com/976583).
return num_of_frame_sinks_with_fixed_interval +
num_of_frame_sinks_with_no_preference >
1;
}
void FrameRateDecider::SetPreferredInterval(
base::TimeDelta new_preferred_interval) {
TRACE_EVENT_INSTANT1("viz", "FrameRateDecider::SetPreferredInterval",
TRACE_EVENT_SCOPE_THREAD, "new_preferred_interval",
new_preferred_interval.InMillisecondsF());
if (AreAlmostEqual(new_preferred_interval,
last_computed_preferred_frame_interval_)) {
num_of_frames_since_preferred_interval_changed_++;
} else {
num_of_frames_since_preferred_interval_changed_ = 0u;
}
last_computed_preferred_frame_interval_ = new_preferred_interval;
if (AreAlmostEqual(current_preferred_frame_interval_, new_preferred_interval))
return;
// The min num of frames heuristic is to ensure we see a constant pattern
// before toggling the global setting to avoid unnecessary switches when
// lowering the refresh rate. For increasing the refresh rate we toggle
// immediately to prioritize smoothness.
bool should_toggle =
current_preferred_frame_interval_ > new_preferred_interval ||
num_of_frames_since_preferred_interval_changed_ >=
min_num_of_frames_to_toggle_interval_;
if (should_toggle) {
current_preferred_frame_interval_ = new_preferred_interval;
client_->SetPreferredFrameInterval(new_preferred_interval);
}
}
bool FrameRateDecider::multiple_refresh_rates_supported() const {
return supports_set_frame_rate_ || supported_intervals_.size() > 1u;
}
} // namespace viz