|  | // 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 "build/build_config.h" | 
|  | #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 { | 
|  |  | 
|  | // The minimum number of frames for which a frame interval preference should | 
|  | // persist before we toggle to it. This is only applied when lowering the frame | 
|  | // rate. If the new preference is higher than the current setting, it is applied | 
|  | // immediately. | 
|  | constexpr size_t kMinNumOfFramesToToggleInterval = 6; | 
|  |  | 
|  | 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) | 
|  | : supported_intervals_{BeginFrameArgs::DefaultInterval()}, | 
|  | min_num_of_frames_to_toggle_interval_(kMinNumOfFramesToToggleInterval), | 
|  | 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 |