diff --git a/ash/BUILD.gn b/ash/BUILD.gn index 5e947c3b45cc36..cf7410aa3495ab 100644 --- a/ash/BUILD.gn +++ b/ash/BUILD.gn @@ -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", diff --git a/ash/frame_throttler/frame_throttling_controller.cc b/ash/frame_throttler/frame_throttling_controller.cc new file mode 100644 index 00000000000000..20f1c95c65a452 --- /dev/null +++ b/ash/frame_throttler/frame_throttling_controller.cc @@ -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* 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& windows, + std::vector* frame_sink_ids) { + for (auto* window : windows) { + if (ash::AppType::BROWSER == static_cast(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& windows, + uint8_t fps) { + std::vector 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& 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 diff --git a/ash/frame_throttler/frame_throttling_controller.h b/ash/frame_throttler/frame_throttling_controller.h new file mode 100644 index 00000000000000..7b32cf1ed699a9 --- /dev/null +++ b/ash/frame_throttler/frame_throttling_controller.h @@ -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 +#include +#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& windows, + uint8_t fps = kDefaultThrottleFps); + // Ends throttling of all throttled windows. + void EndThrottling(); + + private: + void StartThrottling(const std::vector& 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_ diff --git a/ash/shell.cc b/ash/shell.cc index fcde4509ae007a..349cfd832f2cdf 100644 --- a/ash/shell.cc +++ b/ash/shell.cc @@ -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" @@ -1177,6 +1178,8 @@ void Shell::Init( sms_observer_.reset(new SmsObserver()); snap_controller_ = std::make_unique(); key_accessibility_enabler_ = std::make_unique(); + frame_throttling_controller_ = + std::make_unique(context_factory); // Create UserSettingsEventLogger after |system_tray_model_| and // |video_detector_| which it observes. diff --git a/ash/shell.h b/ash/shell.h index 00d4c6c48838b7..94b9a44618ebf4 100644 --- a/ash/shell.h +++ b/ash/shell.h @@ -114,6 +114,7 @@ class EventClientImpl; class EventRewriterControllerImpl; class EventTransformationHandler; class FocusCycler; +class FrameThrottlingController; class HighContrastController; class HighlighterController; class HomeScreenController; @@ -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(); @@ -811,6 +816,8 @@ class ASH_EXPORT Shell : public SessionObserver, // volume keys. std::unique_ptr key_accessibility_enabler_; + std::unique_ptr frame_throttling_controller_; + // For testing only: simulate that a modal window is open bool simulate_modal_window_open_for_test_ = false; diff --git a/ash/wm/overview/overview_controller.cc b/ash/wm/overview/overview_controller.cc index cc6e97283f6f2a..9048d60e16427f 100644 --- a/ash/wm/overview/overview_controller.cc +++ b/ash/wm/overview/overview_controller.cc @@ -7,6 +7,7 @@ #include #include +#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" @@ -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_) diff --git a/components/viz/host/host_frame_sink_manager.cc b/components/viz/host/host_frame_sink_manager.cc index dcfc2efae11520..a98b3a0d224dfd 100644 --- a/components/viz/host/host_frame_sink_manager.cc +++ b/components/viz/host/host_frame_sink_manager.cc @@ -291,6 +291,17 @@ void HostFrameSinkManager::RequestCopyOfOutput( frame_sink_manager_->RequestCopyOfOutput(surface_id, std::move(request)); } +void HostFrameSinkManager::StartThrottling( + const std::vector& 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); diff --git a/components/viz/host/host_frame_sink_manager.h b/components/viz/host/host_frame_sink_manager.h index 44cc99f0d887c8..52ac3aa0df0ab2 100644 --- a/components/viz/host/host_frame_sink_manager.h +++ b/components/viz/host/host_frame_sink_manager.h @@ -171,6 +171,18 @@ class VIZ_HOST_EXPORT HostFrameSinkManager void RequestCopyOfOutput(const SurfaceId& surface_id, std::unique_ptr 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& 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); diff --git a/components/viz/host/host_frame_sink_manager_unittest.cc b/components/viz/host/host_frame_sink_manager_unittest.cc index 2dbfb27d5647fe..857443809f3f96 100644 --- a/components/viz/host/host_frame_sink_manager_unittest.cc +++ b/components/viz/host/host_frame_sink_manager_unittest.cc @@ -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& frame_sink_ids, + base::TimeDelta interval), + (override)); + MOCK_METHOD(void, EndThrottling, (), (override)); }; } // namespace @@ -481,4 +487,14 @@ TEST_F(HostFrameSinkManagerTest, ContextLossRecreateNonRoot) { FlushHostAndVerifyExpectations(); } +TEST_F(HostFrameSinkManagerTest, ThrottleFramePainting) { + const std::vector 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 diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_support.cc b/components/viz/service/frame_sinks/compositor_frame_sink_support.cc index bb23f23c193d78..7e10b2b6e5a35b 100644 --- a/components/viz/service/frame_sinks/compositor_frame_sink_support.cc +++ b/components/viz/service/frame_sinks/compositor_frame_sink_support.cc @@ -38,7 +38,8 @@ enum class SendBeginFrameResult { kSendBlockedEmbedded = 5, kThrottleUndrawnFrames = 6, kSendDefault = 7, - kMaxValue = kSendDefault + kThrottleAsRequested = 8, + kMaxValue = kThrottleAsRequested }; void RecordShouldSendBeginFrame(SendBeginFrameResult result) { @@ -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()); @@ -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; } @@ -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; @@ -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_); @@ -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; } diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_support.h b/components/viz/service/frame_sinks/compositor_frame_sink_support.h index fe2449d8b1390d..3a5f1fa7f13936 100644 --- a/components/viz/service/frame_sinks/compositor_frame_sink_support.h +++ b/components/viz/service/frame_sinks/compositor_frame_sink_support.h @@ -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; @@ -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 pending_surfaces_; diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc b/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc index dacc55a238f230..4b39b205b77e79 100644 --- a/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc +++ b/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc @@ -1455,4 +1455,54 @@ TEST_F(CompositorFrameSinkSupportTest, ThrottleUnresponsiveClient) { support->SetNeedsBeginFrame(false); } + +// Verifies that when CompositorFrameSinkSupport has its |begin_frame_interval_| +// set, any BeginFrame would be sent only after this interval has passed from +// the time when the last BeginFrame was sent. +TEST_F(CompositorFrameSinkSupportTest, BeginFrameInterval) { + FakeExternalBeginFrameSource begin_frame_source(0.f, false); + + testing::NiceMock mock_client; + auto support = std::make_unique( + &mock_client, &manager_, kAnotherArbitraryFrameSinkId, /*is_root=*/true); + SurfaceId id(kAnotherArbitraryFrameSinkId, local_surface_id_); + support->SetBeginFrameSource(&begin_frame_source); + support->SetNeedsBeginFrame(true); + constexpr uint8_t fps = 5; + constexpr base::TimeDelta throttled_interval = + base::TimeDelta::FromSeconds(1) / fps; + support->ThrottleBeginFrame(throttled_interval); + + constexpr base::TimeDelta interval = BeginFrameArgs::DefaultInterval(); + base::TimeTicks frame_time; + uint64_t sequence_number = 1; + int sent_frames = 0; + BeginFrameArgs args; + + const base::TimeTicks end_time = frame_time + base::TimeDelta::FromSeconds(2); + + base::TimeTicks next_expected_begin_frame = frame_time; + while (frame_time < end_time) { + args = CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, + sequence_number++, frame_time); + if (frame_time < next_expected_begin_frame) { + EXPECT_CALL(mock_client, OnBeginFrame(args, _)).Times(0); + } else { + EXPECT_CALL(mock_client, OnBeginFrame(args, _)).WillOnce([&]() { + support->SubmitCompositorFrame(local_surface_id_, + MakeDefaultCompositorFrame()); + GetSurfaceForId(id)->MarkAsDrawn(); + ++sent_frames; + }); + next_expected_begin_frame = throttled_interval + frame_time; + } + begin_frame_source.TestOnBeginFrame(args); + testing::Mock::VerifyAndClearExpectations(&mock_client); + + frame_time += interval; + } + // In total 10 frames should have been sent (5fps x 2 seconds). + EXPECT_EQ(sent_frames, 10); + support->SetNeedsBeginFrame(false); +} } // namespace viz diff --git a/components/viz/service/frame_sinks/frame_sink_manager_impl.cc b/components/viz/service/frame_sinks/frame_sink_manager_impl.cc index 0d71e5fea86e0d..62fee9f8212e2a 100644 --- a/components/viz/service/frame_sinks/frame_sink_manager_impl.cc +++ b/components/viz/service/frame_sinks/frame_sink_manager_impl.cc @@ -626,4 +626,35 @@ void FrameSinkManagerImpl::UpdateDebugRendererSettings( debug_settings_ = debug_settings; } +void FrameSinkManagerImpl::UpdateThrottlingRecursively( + const FrameSinkId& frame_sink_id, + base::TimeDelta interval) { + auto it = support_map_.find(frame_sink_id); + if (it != support_map_.end()) { + it->second->ThrottleBeginFrame(interval); + } + auto children = GetChildrenByParent(frame_sink_id); + for (auto& id : children) + UpdateThrottlingRecursively(id, interval); +} + +void FrameSinkManagerImpl::StartThrottling( + const std::vector& frame_sink_ids, + base::TimeDelta interval) { + DCHECK_GT(interval, base::TimeDelta()); + if (frame_sinks_throttled) + EndThrottling(); + + frame_sinks_throttled = true; + for (auto& frame_sink_id : frame_sink_ids) { + UpdateThrottlingRecursively(frame_sink_id, interval); + } +} + +void FrameSinkManagerImpl::EndThrottling() { + for (auto& support_map_item : support_map_) { + support_map_item.second->ThrottleBeginFrame(base::TimeDelta()); + } + frame_sinks_throttled = false; +} } // namespace viz diff --git a/components/viz/service/frame_sinks/frame_sink_manager_impl.h b/components/viz/service/frame_sinks/frame_sink_manager_impl.h index 569133f4cf7672..9b5c6aeb43c661 100644 --- a/components/viz/service/frame_sinks/frame_sink_manager_impl.h +++ b/components/viz/service/frame_sinks/frame_sink_manager_impl.h @@ -138,6 +138,9 @@ class VIZ_SERVICE_EXPORT FrameSinkManagerImpl EvictBackBufferCallback callback) override; void UpdateDebugRendererSettings( const DebugRendererSettings& debug_settings) override; + void StartThrottling(const std::vector& frame_sink_ids, + base::TimeDelta interval) override; + void EndThrottling() override; // SurfaceObserver implementation. void OnFirstSurfaceActivation(const SurfaceInfo& surface_info) override; @@ -278,6 +281,11 @@ class VIZ_SERVICE_EXPORT FrameSinkManagerImpl bool ChildContains(const FrameSinkId& child_frame_sink_id, const FrameSinkId& search_frame_sink_id) const; + // Updates throttling recursively on a frame sink specified by its |id| + // and all its descendants to send BeginFrames at |interval|. + void UpdateThrottlingRecursively(const FrameSinkId& id, + base::TimeDelta interval); + // SharedBitmapManager for the viz display service for receiving software // resources in CompositorFrameSinks. SharedBitmapManager* const shared_bitmap_manager_; @@ -332,6 +340,9 @@ class VIZ_SERVICE_EXPORT FrameSinkManagerImpl base::flat_map cached_back_buffers_; + // This tells if any frame sinks are currently throttled. + bool frame_sinks_throttled = false; + THREAD_CHECKER(thread_checker_); // |video_detector_| is instantiated lazily in order to avoid overhead on diff --git a/components/viz/service/frame_sinks/frame_sink_manager_unittest.cc b/components/viz/service/frame_sinks/frame_sink_manager_unittest.cc index 396981c381b4bc..76e78d57d40c45 100644 --- a/components/viz/service/frame_sinks/frame_sink_manager_unittest.cc +++ b/components/viz/service/frame_sinks/frame_sink_manager_unittest.cc @@ -31,6 +31,7 @@ constexpr FrameSinkId kFrameSinkIdRoot(1, 1); constexpr FrameSinkId kFrameSinkIdA(2, 1); constexpr FrameSinkId kFrameSinkIdB(3, 1); constexpr FrameSinkId kFrameSinkIdC(4, 1); +constexpr FrameSinkId kFrameSinkIdD(5, 1); // Holds the four interface objects needed to create a RootCompositorFrameSink. struct RootCompositorFrameSinkData { @@ -85,6 +86,11 @@ class FrameSinkManagerTest : public testing::Test { base::Contains(manager_.root_sink_map_, frame_sink_id); } + const base::TimeDelta GetCompositorFrameSinkSupportBeginFrameInterval( + const FrameSinkId& id) { + return manager_.support_map_[id]->begin_frame_interval_; + } + // testing::Test implementation. void TearDown() override { // Make sure that all FrameSinkSourceMappings have been deleted. @@ -398,6 +404,72 @@ TEST_F(FrameSinkManagerTest, DebugLabel) { EXPECT_EQ("", manager_.GetFrameSinkDebugLabel(kFrameSinkIdA)); } +// Verifies the the begin frames are throttled properly for the requested frame +// sinks and their children. +TEST_F(FrameSinkManagerTest, ThrottleBeginFrame) { + // root -> A -> B + // -> C -> D + auto root = CreateCompositorFrameSinkSupport(kFrameSinkIdRoot); + auto client_a = CreateCompositorFrameSinkSupport(kFrameSinkIdA); + auto client_b = CreateCompositorFrameSinkSupport(kFrameSinkIdB); + auto client_c = CreateCompositorFrameSinkSupport(kFrameSinkIdC); + auto client_d = CreateCompositorFrameSinkSupport(kFrameSinkIdD); + + // Set up the hierarchy. + manager_.RegisterFrameSinkHierarchy(root->frame_sink_id(), + client_a->frame_sink_id()); + manager_.RegisterFrameSinkHierarchy(client_a->frame_sink_id(), + client_b->frame_sink_id()); + manager_.RegisterFrameSinkHierarchy(root->frame_sink_id(), + client_c->frame_sink_id()); + manager_.RegisterFrameSinkHierarchy(client_c->frame_sink_id(), + client_d->frame_sink_id()); + + constexpr base::TimeDelta interval = base::TimeDelta::FromSeconds(1) / 20; + + std::vector ids{kFrameSinkIdRoot, kFrameSinkIdA, kFrameSinkIdB, + kFrameSinkIdC, kFrameSinkIdD}; + + // By default, a CompositorFrameSinkSupport shouldn't have its + // |begin_frame_interval| set. + for (auto& id : ids) { + EXPECT_EQ(GetCompositorFrameSinkSupportBeginFrameInterval(id), + base::TimeDelta()); + } + + manager_.StartThrottling({kFrameSinkIdRoot}, interval); + for (auto& id : ids) { + EXPECT_EQ(GetCompositorFrameSinkSupportBeginFrameInterval(id), interval); + } + + manager_.EndThrottling(); + for (auto& id : ids) { + EXPECT_EQ(GetCompositorFrameSinkSupportBeginFrameInterval(id), + base::TimeDelta()); + } + + manager_.StartThrottling({kFrameSinkIdB, kFrameSinkIdC}, interval); + ids = {kFrameSinkIdB, kFrameSinkIdC, kFrameSinkIdD}; + for (auto& id : ids) { + EXPECT_EQ(GetCompositorFrameSinkSupportBeginFrameInterval(id), interval); + } + + manager_.EndThrottling(); + for (auto& id : ids) { + EXPECT_EQ(GetCompositorFrameSinkSupportBeginFrameInterval(id), + base::TimeDelta()); + } + + manager_.UnregisterFrameSinkHierarchy(root->frame_sink_id(), + client_a->frame_sink_id()); + manager_.UnregisterFrameSinkHierarchy(client_a->frame_sink_id(), + client_b->frame_sink_id()); + manager_.UnregisterFrameSinkHierarchy(root->frame_sink_id(), + client_c->frame_sink_id()); + manager_.UnregisterFrameSinkHierarchy(client_c->frame_sink_id(), + client_d->frame_sink_id()); +} + namespace { enum RegisterOrder { REGISTER_HIERARCHY_FIRST, REGISTER_CLIENTS_FIRST }; diff --git a/components/viz/test/test_frame_sink_manager.h b/components/viz/test/test_frame_sink_manager.h index b9d3c6d88cefb7..ca02f1e33d3cff 100644 --- a/components/viz/test/test_frame_sink_manager.h +++ b/components/viz/test/test_frame_sink_manager.h @@ -62,6 +62,9 @@ class TestFrameSinkManagerImpl : public mojom::FrameSinkManager { EvictBackBufferCallback callback) override {} void UpdateDebugRendererSettings( const DebugRendererSettings& debug_settings) override {} + void StartThrottling(const std::vector& frame_sink_ids, + base::TimeDelta interval) override {} + void EndThrottling() override {} mojo::Receiver receiver_{this}; mojo::Remote client_; diff --git a/services/viz/privileged/mojom/compositing/frame_sink_manager.mojom b/services/viz/privileged/mojom/compositing/frame_sink_manager.mojom index 75d54c2d5f6547..e31c2f104b54bf 100644 --- a/services/viz/privileged/mojom/compositing/frame_sink_manager.mojom +++ b/services/viz/privileged/mojom/compositing/frame_sink_manager.mojom @@ -122,6 +122,19 @@ interface FrameSinkManager { // Marks the given SurfaceIds for destruction. EvictSurfaces(array surface_ids); + // 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. + StartThrottling( + array frame_sink_ids, + mojo_base.mojom.TimeDelta interval); + + // Ends all previously throttled frame sinks. + EndThrottling(); + // Takes a snapshot of |surface_id| or a newer surface with the same // FrameSinkId. The request will be queued up until such surface exists and is // reachable from the root surface. diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml index 55fe4c205e9874..b0d6abe955d74d 100644 --- a/tools/metrics/histograms/enums.xml +++ b/tools/metrics/histograms/enums.xml @@ -62550,6 +62550,7 @@ https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf +