Skip to content

Commit

Permalink
Frame throttling in overview mode (browsers only)
Browse files Browse the repository at this point in the history
Ideally, frame submission is expected to be at full rate of 60fps.
However, in many scenarios, this is not necessary. For example, when a
window is (partially) occluded by another window, or when a window is
behind a backdrop filter that blurs its content, or when a window is
going through an animation (e.g. going in or out of overview mode). In
such cases, we'd like to submit frames to a lower fps to improve
performance - this is where the frame throttling comes in.

This changelist only deals with browser windows, and when UI is going
into and out of overview mode. We'd like to throttle the BeginFrames
(ShouldSendBeginFrames), so that all the browser windows in overview
mode will update at a reasonable lower fps.

Here is the design doc if you'd like to inspect more.
https://docs.google.com/document/d/1kvCIuA_xsh1VAsFiC5i1paAtjkGm2z-hFhFXWYfL7Sg/edit?usp=sharing

Bug: 1095670
Change-Id: I8c9697fa5c0886032525395355d3c8ce76e439cd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2246716
Reviewed-by: kylechar <kylechar@chromium.org>
Reviewed-by: Mitsuru Oshima <oshima@chromium.org>
Reviewed-by: Will Harris <wfh@chromium.org>
Commit-Queue: Jun Liu <yjliu@chromium.org>
Auto-Submit: Jun Liu <yjliu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#789550}
  • Loading branch information
yjliu authored and Commit Bot committed Jul 17, 2020
1 parent 06bec5c commit 10679cf
Show file tree
Hide file tree
Showing 18 changed files with 392 additions and 9 deletions.
2 changes: 2 additions & 0 deletions ash/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,8 @@ component("ash") {
"frame/snap_controller_impl.cc",
"frame/snap_controller_impl.h",
"frame/wide_frame_view.cc",
"frame_throttler/frame_throttling_controller.cc",
"frame_throttler/frame_throttling_controller.h",
"high_contrast/high_contrast_controller.cc",
"high_contrast/high_contrast_controller.h",
"highlighter/highlighter_controller.cc",
Expand Down
72 changes: 72 additions & 0 deletions ash/frame_throttler/frame_throttling_controller.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright 2020 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 "ash/frame_throttler/frame_throttling_controller.h"
#include "ash/public/cpp/app_types.h"
#include "components/viz/common/surfaces/frame_sink_id.h"
#include "components/viz/host/host_frame_sink_manager.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/window.h"

namespace ash {

namespace {

void CollectFrameSinkIds(const aura::Window* window,
std::vector<viz::FrameSinkId>* frame_sink_ids) {
if (window->GetFrameSinkId().is_valid()) {
frame_sink_ids->push_back(window->GetFrameSinkId());
return;
}
for (auto* child : window->children()) {
CollectFrameSinkIds(child, frame_sink_ids);
}
}

void CollectBrowserFrameSinkIds(const std::vector<aura::Window*>& windows,
std::vector<viz::FrameSinkId>* frame_sink_ids) {
for (auto* window : windows) {
if (ash::AppType::BROWSER == static_cast<ash::AppType>(window->GetProperty(
aura::client::kAppType))) {
CollectFrameSinkIds(window, frame_sink_ids);
}
}
}

} // namespace

FrameThrottlingController::FrameThrottlingController(
ui::ContextFactory* context_factory)
: context_factory_(context_factory) {}

FrameThrottlingController::~FrameThrottlingController() {
EndThrottling();
}

void FrameThrottlingController::StartThrottling(
const std::vector<aura::Window*>& windows,
uint8_t fps) {
std::vector<viz::FrameSinkId> frame_sink_ids;
frame_sink_ids.reserve(windows.size());

CollectBrowserFrameSinkIds(windows, &frame_sink_ids);
StartThrottling(frame_sink_ids, fps);
}

void FrameThrottlingController::StartThrottling(
const std::vector<viz::FrameSinkId>& frame_sink_ids,
uint8_t fps) {
DCHECK_GT(fps, 0);
if (context_factory_ && !frame_sink_ids.empty()) {
context_factory_->GetHostFrameSinkManager()->StartThrottling(
frame_sink_ids, base::TimeDelta::FromSeconds(1) / fps);
}
}

void FrameThrottlingController::EndThrottling() {
if (context_factory_)
context_factory_->GetHostFrameSinkManager()->EndThrottling();
}

} // namespace ash
49 changes: 49 additions & 0 deletions ash/frame_throttler/frame_throttling_controller.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2020 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.

#ifndef ASH_FRAME_THROTTLER_FRAME_THROTTLING_CONTROLLER_H_
#define ASH_FRAME_THROTTLER_FRAME_THROTTLING_CONTROLLER_H_

#include <stdint.h>
#include <vector>
#include "ash/ash_export.h"
#include "base/macros.h"
#include "components/viz/common/surfaces/frame_sink_id.h"

namespace aura {
class Window;
}

namespace ui {
class ContextFactory;
}

namespace ash {

constexpr uint8_t kDefaultThrottleFps = 20;

class ASH_EXPORT FrameThrottlingController {
public:
explicit FrameThrottlingController(ui::ContextFactory* context_factory);
~FrameThrottlingController();

// Starts to throttle the framerate of |windows|.
void StartThrottling(const std::vector<aura::Window*>& windows,
uint8_t fps = kDefaultThrottleFps);
// Ends throttling of all throttled windows.
void EndThrottling();

private:
void StartThrottling(const std::vector<viz::FrameSinkId>& frame_sink_ids,
uint8_t fps);

ui::ContextFactory* context_factory_;

friend class FrameThrottlingControllerTest;
DISALLOW_COPY_AND_ASSIGN(FrameThrottlingController);
};

} // namespace ash

#endif // ASH_FRAME_THROTTLER_FRAME_THROTTLING_CONTROLLER_H_
3 changes: 3 additions & 0 deletions ash/shell.cc
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
#include "ash/focus_cycler.h"
#include "ash/frame/non_client_frame_view_ash.h"
#include "ash/frame/snap_controller_impl.h"
#include "ash/frame_throttler/frame_throttling_controller.h"
#include "ash/high_contrast/high_contrast_controller.h"
#include "ash/highlighter/highlighter_controller.h"
#include "ash/home_screen/home_screen_controller.h"
Expand Down Expand Up @@ -1177,6 +1178,8 @@ void Shell::Init(
sms_observer_.reset(new SmsObserver());
snap_controller_ = std::make_unique<SnapControllerImpl>();
key_accessibility_enabler_ = std::make_unique<KeyAccessibilityEnabler>();
frame_throttling_controller_ =
std::make_unique<FrameThrottlingController>(context_factory);

// Create UserSettingsEventLogger after |system_tray_model_| and
// |video_detector_| which it observes.
Expand Down
7 changes: 7 additions & 0 deletions ash/shell.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ class EventClientImpl;
class EventRewriterControllerImpl;
class EventTransformationHandler;
class FocusCycler;
class FrameThrottlingController;
class HighContrastController;
class HighlighterController;
class HomeScreenController;
Expand Down Expand Up @@ -531,6 +532,10 @@ class ASH_EXPORT Shell : public SessionObserver,

PrefService* local_state() { return local_state_; }

FrameThrottlingController* frame_throttling_controller() {
return frame_throttling_controller_.get();
}

// Force the shelf to query for it's current visibility state.
// TODO(jamescook): Move to Shelf.
void UpdateShelfVisibility();
Expand Down Expand Up @@ -811,6 +816,8 @@ class ASH_EXPORT Shell : public SessionObserver,
// volume keys.
std::unique_ptr<KeyAccessibilityEnabler> key_accessibility_enabler_;

std::unique_ptr<FrameThrottlingController> frame_throttling_controller_;

// For testing only: simulate that a modal window is open
bool simulate_modal_window_open_for_test_ = false;

Expand Down
3 changes: 3 additions & 0 deletions ash/wm/overview/overview_controller.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <algorithm>
#include <utility>

#include "ash/frame_throttler/frame_throttling_controller.h"
#include "ash/keyboard/ui/keyboard_ui_controller.h"
#include "ash/public/cpp/ash_features.h"
#include "ash/public/cpp/window_properties.h"
Expand Down Expand Up @@ -369,10 +370,12 @@ void OverviewController::ToggleOverview(OverviewEnterExitType type) {
observer.OnOverviewModeEnded();
if (!should_end_immediately && delayed_animations_.empty())
OnEndingAnimationComplete(/*canceled=*/false);
Shell::Get()->frame_throttling_controller()->EndThrottling();
} else {
DCHECK(CanEnterOverview());
TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("ui", "OverviewController::EnterOverview",
this);
Shell::Get()->frame_throttling_controller()->StartThrottling(windows);

// Clear any animations that may be running from last overview end.
for (const auto& animation : delayed_animations_)
Expand Down
11 changes: 11 additions & 0 deletions components/viz/host/host_frame_sink_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,17 @@ void HostFrameSinkManager::RequestCopyOfOutput(
frame_sink_manager_->RequestCopyOfOutput(surface_id, std::move(request));
}

void HostFrameSinkManager::StartThrottling(
const std::vector<FrameSinkId>& frame_sink_ids,
base::TimeDelta interval) {
DCHECK_GT(interval, base::TimeDelta());
frame_sink_manager_->StartThrottling(frame_sink_ids, interval);
}

void HostFrameSinkManager::EndThrottling() {
frame_sink_manager_->EndThrottling();
}

void HostFrameSinkManager::AddHitTestRegionObserver(
HitTestRegionObserver* observer) {
observers_.AddObserver(observer);
Expand Down
12 changes: 12 additions & 0 deletions components/viz/host/host_frame_sink_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,18 @@ class VIZ_HOST_EXPORT HostFrameSinkManager
void RequestCopyOfOutput(const SurfaceId& surface_id,
std::unique_ptr<CopyOutputRequest> request);

// Starts throttling the frame sinks specified by |frame_sink_ids| and all
// their descendant sinks to send BeginFrames at an interval of |interval|.
// |interval| should be greater than zero. Calling this function before
// calling EndThrottling() to end a previous throttling operation will
// automatically end the previous operation before applying the current
// throttling operation.
void StartThrottling(const std::vector<FrameSinkId>& frame_sink_ids,
base::TimeDelta interval);

// Ends throttling of all previously throttled frame sinks.
void EndThrottling();

// Add/Remove an observer to receive notifications of when the host receives
// new hit test data.
void AddHitTestRegionObserver(HitTestRegionObserver* observer);
Expand Down
16 changes: 16 additions & 0 deletions components/viz/host/host_frame_sink_manager_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ class MockFrameSinkManagerImpl : public TestFrameSinkManagerImpl {
void(const FrameSinkId& parent, const FrameSinkId& child));
MOCK_METHOD2(UnregisterFrameSinkHierarchy,
void(const FrameSinkId& parent, const FrameSinkId& child));
MOCK_METHOD(void,
StartThrottling,
(const std::vector<FrameSinkId>& frame_sink_ids,
base::TimeDelta interval),
(override));
MOCK_METHOD(void, EndThrottling, (), (override));
};

} // namespace
Expand Down Expand Up @@ -481,4 +487,14 @@ TEST_F(HostFrameSinkManagerTest, ContextLossRecreateNonRoot) {
FlushHostAndVerifyExpectations();
}

TEST_F(HostFrameSinkManagerTest, ThrottleFramePainting) {
const std::vector<FrameSinkId> frame_sink_ids{
FrameSinkId(1, 1), FrameSinkId(2, 2), FrameSinkId(3, 3)};
constexpr base::TimeDelta interval = base::TimeDelta::FromSeconds(1) / 10;
EXPECT_CALL(impl(), StartThrottling(frame_sink_ids, interval)).Times(1);
host().StartThrottling(frame_sink_ids, interval);
EXPECT_CALL(impl(), EndThrottling()).Times(1);
host().EndThrottling();
FlushHostAndVerifyExpectations();
}
} // namespace viz
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ enum class SendBeginFrameResult {
kSendBlockedEmbedded = 5,
kThrottleUndrawnFrames = 6,
kSendDefault = 7,
kMaxValue = kSendDefault
kThrottleAsRequested = 8,
kMaxValue = kThrottleAsRequested
};

void RecordShouldSendBeginFrame(SendBeginFrameResult result) {
Expand Down Expand Up @@ -144,6 +145,10 @@ void CompositorFrameSinkSupport::SetBeginFrameSource(
UpdateNeedsBeginFramesInternal();
}

void CompositorFrameSinkSupport::ThrottleBeginFrame(base::TimeDelta interval) {
begin_frame_interval_ = interval;
}

void CompositorFrameSinkSupport::OnSurfaceActivated(Surface* surface) {
DCHECK(surface);
DCHECK(surface->HasActiveFrame());
Expand Down Expand Up @@ -833,9 +838,21 @@ int64_t CompositorFrameSinkSupport::ComputeTraceId() {

bool CompositorFrameSinkSupport::ShouldSendBeginFrame(
base::TimeTicks frame_time) {
// We should throttle OnBeginFrame() if it has been less than
// |begin_frame_interval_| since the last one was sent because clients have
// requested to update at such rate.
const bool should_throttle_as_requested =
begin_frame_interval_ > base::TimeDelta() &&
(frame_time - last_frame_time_) < begin_frame_interval_;
// We might throttle this OnBeginFrame() if it's been less than a second since
// the last one was sent, either because clients are unresponsive or have
// submitted too many undrawn frames.
const bool can_throttle_if_unresponsive_or_excessive =
frame_time - last_frame_time_ < base::TimeDelta::FromSeconds(1);

// If there are pending timing details from the previous frame(s),
// then the client needs to receive the begin-frame.
if (!frame_timing_details_.empty()) {
if (!frame_timing_details_.empty() && !should_throttle_as_requested) {
RecordShouldSendBeginFrame(SendBeginFrameResult::kSendFrameTiming);
return true;
}
Expand All @@ -851,13 +868,9 @@ bool CompositorFrameSinkSupport::ShouldSendBeginFrame(
return false;
}

// We might throttle this OnBeginFrame() if it's been less than a second since
// the last one was sent.
bool can_throttle =
(frame_time - last_frame_time_) < base::TimeDelta::FromSeconds(1);

// Throttle clients that are unresponsive.
if (can_throttle && begin_frame_tracker_.ShouldThrottleBeginFrame()) {
if (can_throttle_if_unresponsive_or_excessive &&
begin_frame_tracker_.ShouldThrottleBeginFrame()) {
RecordShouldSendBeginFrame(
SendBeginFrameResult::kThrottleUnresponsiveClient);
return false;
Expand All @@ -875,6 +888,11 @@ bool CompositorFrameSinkSupport::ShouldSendBeginFrame(
return true;
}

if (should_throttle_as_requested) {
RecordShouldSendBeginFrame(SendBeginFrameResult::kThrottleAsRequested);
return false;
}

Surface* surface =
surface_manager_->GetSurfaceForId(last_activated_surface_id_);

Expand All @@ -890,7 +908,8 @@ bool CompositorFrameSinkSupport::ShouldSendBeginFrame(

// Throttle clients that have submitted too many undrawn frames.
uint64_t num_undrawn_frames = active_frame_index - last_drawn_frame_index_;
if (can_throttle && num_undrawn_frames > kUndrawnFrameLimit) {
if (can_throttle_if_unresponsive_or_excessive &&
num_undrawn_frames > kUndrawnFrameLimit) {
RecordShouldSendBeginFrame(SendBeginFrameResult::kThrottleUndrawnFrames);
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ class VIZ_SERVICE_EXPORT CompositorFrameSinkSupport
base::TimeDelta GetPreferredFrameInterval(
mojom::CompositorFrameSinkType* type) const;
void InitializeCompositorFrameSinkType(mojom::CompositorFrameSinkType type);
// Throttles the BeginFrames to send at |interval| if |interval| is greater
// than zero, or clears previously set throttle if zero.
void ThrottleBeginFrame(base::TimeDelta interval);

// SurfaceClient implementation.
void OnSurfaceActivated(Surface* surface) override;
Expand Down Expand Up @@ -341,6 +344,11 @@ class VIZ_SERVICE_EXPORT CompositorFrameSinkSupport
"|last_drawn_frame_index| relies on kFrameIndexStart > 1");
uint64_t last_drawn_frame_index_ = kFrameIndexStart - 1;

// This value represents throttling on sending a BeginFrame. If non-zero, it
// represents the duration of time in between sending two consecutive frames.
// If zero, no throttling would be applied.
base::TimeDelta begin_frame_interval_;

// The set of surfaces owned by this frame sink that have pending frame.
base::flat_set<Surface*> pending_surfaces_;

Expand Down
Loading

0 comments on commit 10679cf

Please sign in to comment.