From a98c7e43efb0b062c1bb0b5d7b4f59ae0b7954cd Mon Sep 17 00:00:00 2001 From: yjliu Date: Fri, 26 Feb 2021 00:34:51 +0000 Subject: [PATCH] Reland "Compositing-based frame throttling." This relands commit d48bbee59945e6ea45e2f5816a270cf90af73ae5. A number of Asan tests failed on the reverted commit due to the use of ScopedMultiSourceObservation without properly removing the sources (WindowTreeHost*) when they go out of scope at places other than when the hosts are not deleted from WindowHostManager. This commit simplifies the observer pattern used on FrameThrottlingController as aura::WindowTreeHostObserver. Bug: 1143872,1179740,1179695 Change-Id: Ifaf903af7c824c5609ada25013bd4977e5086910 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2705310 Commit-Queue: Jun Liu Auto-Submit: Jun Liu Reviewed-by: Mitsuru Oshima Reviewed-by: kylechar Reviewed-by: Scott Violet Reviewed-by: Will Harris Cr-Commit-Position: refs/heads/master@{#857938} --- ash/display/window_tree_host_manager.cc | 2 + .../frame_throttling_controller.cc | 57 +++++++++ .../frame_throttling_controller.h | 31 ++++- ash/shell.cc | 5 +- cc/BUILD.gn | 3 + cc/test/fake_layer_tree_host_impl_client.h | 2 + cc/trees/layer_tree_host_impl.cc | 10 ++ cc/trees/layer_tree_host_impl.h | 8 ++ cc/trees/layer_tree_host_impl_unittest.cc | 2 + .../layer_tree_host_single_thread_client.h | 8 ++ cc/trees/proxy_impl.cc | 5 + cc/trees/proxy_impl.h | 4 +- cc/trees/single_thread_proxy.cc | 14 ++- cc/trees/single_thread_proxy.h | 3 + cc/trees/throttle_decider.cc | 92 +++++++++++++++ cc/trees/throttle_decider.h | 48 ++++++++ cc/trees/throttle_decider_unittest.cc | 111 ++++++++++++++++++ .../viz/host/host_frame_sink_manager.cc | 5 + components/viz/host/host_frame_sink_manager.h | 2 + .../host/host_frame_sink_manager_unittest.cc | 7 ++ .../frame_sinks/frame_sink_manager_impl.cc | 13 ++ .../frame_sinks/frame_sink_manager_impl.h | 2 + .../frame_sink_manager_unittest.cc | 69 +++++++++++ components/viz/test/test_frame_sink_manager.h | 6 + .../compositing/frame_sink_manager.mojom | 6 + ui/aura/window_tree_host.cc | 6 + ui/aura/window_tree_host.h | 8 +- ui/aura/window_tree_host_observer.h | 6 + ui/compositor/compositor.cc | 7 ++ ui/compositor/compositor.h | 5 +- ui/compositor/compositor_observer.h | 5 + 31 files changed, 539 insertions(+), 13 deletions(-) create mode 100644 cc/trees/throttle_decider.cc create mode 100644 cc/trees/throttle_decider.h create mode 100644 cc/trees/throttle_decider_unittest.cc diff --git a/ash/display/window_tree_host_manager.cc b/ash/display/window_tree_host_manager.cc index b7539f3b6324eb..3441a4988c7223 100644 --- a/ash/display/window_tree_host_manager.cc +++ b/ash/display/window_tree_host_manager.cc @@ -13,6 +13,7 @@ #include "ash/display/cursor_window_controller.h" #include "ash/display/mirror_window_controller.h" #include "ash/display/root_window_transformers.h" +#include "ash/frame_throttler/frame_throttling_controller.h" #include "ash/host/ash_window_tree_host.h" #include "ash/host/ash_window_tree_host_init_params.h" #include "ash/host/root_window_transformer.h" @@ -868,6 +869,7 @@ AshWindowTreeHost* WindowTreeHostManager::AddWindowTreeHostForDisplay( AshWindowTreeHost* ash_host = AshWindowTreeHost::Create(params_with_bounds).release(); aura::WindowTreeHost* host = ash_host->AsWindowTreeHost(); + Shell::Get()->frame_throttling_controller()->OnWindowTreeHostCreated(host); DCHECK(!host->has_input_method()); if (!input_method_) { // Singleton input method instance for Ash. input_method_ = ui::CreateInputMethod(this, host->GetAcceleratedWidget()); diff --git a/ash/frame_throttler/frame_throttling_controller.cc b/ash/frame_throttler/frame_throttling_controller.cc index b008b6f16a4e0c..3a7fca342e5dfd 100644 --- a/ash/frame_throttler/frame_throttling_controller.cc +++ b/ash/frame_throttler/frame_throttling_controller.cc @@ -16,6 +16,7 @@ #include "components/viz/host/host_frame_sink_manager.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/window.h" +#include "ui/aura/window_tree_host.h" namespace ash { @@ -42,6 +43,28 @@ void CollectBrowserFrameSinkIds(const std::vector& windows, } } +// Recursively walks through all descendents of |window| and collects those +// belonging to a browser and with their frame sink ids being a member of |ids|. +// |inside_browser| indicates if the |window| already belongs to a browser. +// |browser_ids| is the output containing the result frame sinks ids. +void CollectBrowserFrameSinkIdsInWindow( + const aura::Window* window, + bool inside_browser, + const base::flat_set& ids, + base::flat_set* browser_ids) { + if (inside_browser || ash::AppType::BROWSER == + static_cast( + window->GetProperty(aura::client::kAppType))) { + const auto& id = window->GetFrameSinkId(); + if (id.is_valid() && ids.contains(id)) + browser_ids->insert(id); + inside_browser = true; + } + + for (auto* child : window->children()) + CollectBrowserFrameSinkIdsInWindow(child, inside_browser, ids, browser_ids); +} + } // namespace FrameThrottlingController::FrameThrottlingController( @@ -157,6 +180,40 @@ void FrameThrottlingController::EndThrottling() { windows_throttled_ = false; } +void FrameThrottlingController::OnCompositingFrameSinksToThrottleUpdated( + const aura::WindowTreeHost* window_tree_host, + const base::flat_set& ids) { + base::flat_set& browser_ids = + host_to_ids_map_[window_tree_host]; + browser_ids.clear(); + CollectBrowserFrameSinkIdsInWindow(window_tree_host->window(), false, ids, + &browser_ids); + UpdateThrottling(); +} + +void FrameThrottlingController::OnWindowDestroying(aura::Window* window) { + DCHECK(window->IsRootWindow()); + host_to_ids_map_.erase(window->GetHost()); + UpdateThrottling(); + window->RemoveObserver(this); +} + +void FrameThrottlingController::OnWindowTreeHostCreated( + aura::WindowTreeHost* host) { + host->AddObserver(this); + host->window()->AddObserver(this); +} + +void FrameThrottlingController::UpdateThrottling() { + std::vector ids_to_throttle; + for (const auto& pair : host_to_ids_map_) { + ids_to_throttle.insert(ids_to_throttle.end(), pair.second.begin(), + pair.second.end()); + } + context_factory_->GetHostFrameSinkManager()->Throttle( + ids_to_throttle, base::TimeDelta::FromHz(throttled_fps_)); +} + void FrameThrottlingController::AddObserver(FrameThrottlingObserver* observer) { observers_.AddObserver(observer); } diff --git a/ash/frame_throttler/frame_throttling_controller.h b/ash/frame_throttler/frame_throttling_controller.h index 323d1f99afa368..bd677f1ba64585 100644 --- a/ash/frame_throttler/frame_throttling_controller.h +++ b/ash/frame_throttler/frame_throttling_controller.h @@ -10,12 +10,17 @@ #include "ash/ash_export.h" #include "ash/frame_throttler/frame_throttling_observer.h" +#include "base/containers/flat_map.h" +#include "base/containers/flat_set.h" #include "base/macros.h" #include "base/observer_list.h" #include "base/observer_list_types.h" #include "components/viz/common/surfaces/frame_sink_id.h" +#include "ui/aura/window_observer.h" +#include "ui/aura/window_tree_host_observer.h" namespace aura { +class WindowTreeHost; class Window; } @@ -27,13 +32,25 @@ namespace ash { constexpr uint8_t kDefaultThrottleFps = 20; -class ASH_EXPORT FrameThrottlingController { +class ASH_EXPORT FrameThrottlingController + : public aura::WindowTreeHostObserver, + public aura::WindowObserver { public: explicit FrameThrottlingController(ui::ContextFactory* context_factory); FrameThrottlingController(const FrameThrottlingController&) = delete; FrameThrottlingController& operator=(const FrameThrottlingController&) = delete; - ~FrameThrottlingController(); + ~FrameThrottlingController() final; + + // ui::WindowTreeHostObserver overrides + void OnCompositingFrameSinksToThrottleUpdated( + const aura::WindowTreeHost* host, + const base::flat_set& ids) override; + + // ui::WindowObserver overrides + void OnWindowDestroying(aura::Window* window) override; + + void OnWindowTreeHostCreated(aura::WindowTreeHost* host); // Starts to throttle the framerate of |windows|. void StartThrottling(const std::vector& windows); @@ -55,10 +72,20 @@ class ASH_EXPORT FrameThrottlingController { void EndThrottlingFrameSinks(); void EndThrottlingArc(); + void UpdateThrottling(); + ui::ContextFactory* context_factory_ = nullptr; base::ObserverList observers_; base::ObserverList arc_observers_; + // Maps aura::WindowTreeHost* to a set of FrameSinkIds to be throttled. + using WindowTreeHostMap = base::flat_map>; + // Compositing-based throttling updates the set of FrameSinkIds per tree and + // this map keeps each aura::WindowTreeHost* to the most recent updated + // FrameSinkIds. + WindowTreeHostMap host_to_ids_map_; + // The fps used for throttling. uint8_t throttled_fps_ = kDefaultThrottleFps; bool windows_throttled_ = false; diff --git a/ash/shell.cc b/ash/shell.cc index e83606b67db209..91fd61c24f24d7 100644 --- a/ash/shell.cc +++ b/ash/shell.cc @@ -1025,6 +1025,9 @@ void Shell::Init( screen_position_controller_ = std::make_unique(); + frame_throttling_controller_ = + std::make_unique(context_factory); + window_tree_host_manager_->Start(); AshWindowTreeHostInitParams ash_init_params; window_tree_host_manager_->CreatePrimaryHost(ash_init_params); @@ -1250,8 +1253,6 @@ 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/cc/BUILD.gn b/cc/BUILD.gn index cbc570bc154798..3465bf70b6c036 100644 --- a/cc/BUILD.gn +++ b/cc/BUILD.gn @@ -412,6 +412,8 @@ cc_component("cc") { "trees/target_property.h", "trees/task_runner_provider.cc", "trees/task_runner_provider.h", + "trees/throttle_decider.cc", + "trees/throttle_decider.h", "trees/transform_node.cc", "trees/transform_node.h", "trees/tree_synchronizer.cc", @@ -792,6 +794,7 @@ cc_test("cc_unittests") { "trees/property_tree_builder_unittest.cc", "trees/property_tree_unittest.cc", "trees/swap_promise_manager_unittest.cc", + "trees/throttle_decider_unittest.cc", "trees/tree_synchronizer_unittest.cc", "trees/ukm_manager_unittest.cc", diff --git a/cc/test/fake_layer_tree_host_impl_client.h b/cc/test/fake_layer_tree_host_impl_client.h index 44c280ec44c570..3e2f5740645648 100644 --- a/cc/test/fake_layer_tree_host_impl_client.h +++ b/cc/test/fake_layer_tree_host_impl_client.h @@ -58,6 +58,8 @@ class FakeLayerTreeHostImplClient : public LayerTreeHostImplClient { base::TimeDelta first_scroll_delay, base::TimeTicks first_scroll_timestamp) override {} bool IsInSynchronousComposite() const override; + void FrameSinksToThrottleUpdated( + const base::flat_set& ids) override {} void reset_did_request_impl_side_invalidation() { did_request_impl_side_invalidation_ = false; diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc index 1c83db3524a423..f16b213a9e15dc 100644 --- a/cc/trees/layer_tree_host_impl.cc +++ b/cc/trees/layer_tree_host_impl.cc @@ -1230,6 +1230,8 @@ DrawResult LayerTreeHostImpl::CalculateRenderPasses(FrameData* frame) { // Advance our de-jelly state. This is a no-op if de-jelly is not active. de_jelly_state_.AdvanceFrame(active_tree_.get()); + if (settings_.enable_compositing_based_throttling) + throttle_decider_.Prepare(); for (EffectTreeLayerListIterator it(active_tree()); it.state() != EffectTreeLayerListIterator::State::END; ++it) { auto target_render_pass_id = it.target_render_surface()->render_pass_id(); @@ -1247,6 +1249,8 @@ DrawResult LayerTreeHostImpl::CalculateRenderPasses(FrameData* frame) { render_surface->EffectTreeIndex(), &target_render_pass->copy_requests); } + if (settings_.enable_compositing_based_throttling && target_render_pass) + throttle_decider_.ProcessRenderPass(*target_render_pass); } else if (it.state() == EffectTreeLayerListIterator::State::CONTRIBUTING_SURFACE) { RenderSurfaceImpl* render_surface = it.current_render_surface(); @@ -2414,6 +2418,12 @@ bool LayerTreeHostImpl::DrawLayers(FrameData* frame) { devtools_instrumentation::DidDrawFrame(id_); benchmark_instrumentation::IssueImplThreadRenderingStatsEvent( rendering_stats_instrumentation_->TakeImplThreadRenderingStats()); + + if (settings_.enable_compositing_based_throttling && + throttle_decider_.HasThrottlingChanged()) { + client_->FrameSinksToThrottleUpdated(throttle_decider_.ids()); + } + return true; } diff --git a/cc/trees/layer_tree_host_impl.h b/cc/trees/layer_tree_host_impl.h index 174068623f4072..d62e352010bf36 100644 --- a/cc/trees/layer_tree_host_impl.h +++ b/cc/trees/layer_tree_host_impl.h @@ -63,6 +63,7 @@ #include "cc/trees/presentation_time_callback_buffer.h" #include "cc/trees/render_frame_metadata.h" #include "cc/trees/task_runner_provider.h" +#include "cc/trees/throttle_decider.h" #include "cc/trees/ukm_manager.h" #include "components/viz/client/client_resource_provider.h" #include "components/viz/common/frame_sinks/begin_frame_args.h" @@ -190,6 +191,9 @@ class LayerTreeHostImplClient { // code as a result. virtual bool IsInSynchronousComposite() const = 0; + virtual void FrameSinksToThrottleUpdated( + const base::flat_set& ids) = 0; + protected: virtual ~LayerTreeHostImplClient() = default; }; @@ -1231,6 +1235,10 @@ class CC_EXPORT LayerTreeHostImpl : public TileManagerClient, // mutable because |contains_srgb_cache_| is accessed in a const method. mutable base::MRUCache contains_srgb_cache_; + // When enabled, calculates which frame sinks can be throttled based on + // some pre-defined criteria. + ThrottleDecider throttle_decider_; + // Must be the last member to ensure this is destroyed first in the // destruction order and invalidates all weak pointers. base::WeakPtrFactory weak_factory_{this}; diff --git a/cc/trees/layer_tree_host_impl_unittest.cc b/cc/trees/layer_tree_host_impl_unittest.cc index e543660f2bfc7c..3c78b9ea2eb61c 100644 --- a/cc/trees/layer_tree_host_impl_unittest.cc +++ b/cc/trees/layer_tree_host_impl_unittest.cc @@ -279,6 +279,8 @@ class LayerTreeHostImplTest : public testing::Test, first_scroll_observed++; } bool IsInSynchronousComposite() const override { return false; } + void FrameSinksToThrottleUpdated( + const base::flat_set& ids) override {} void set_reduce_memory_result(bool reduce_memory_result) { reduce_memory_result_ = reduce_memory_result; } diff --git a/cc/trees/layer_tree_host_single_thread_client.h b/cc/trees/layer_tree_host_single_thread_client.h index f2ce8e18597662..f35d3cfd6b2df5 100644 --- a/cc/trees/layer_tree_host_single_thread_client.h +++ b/cc/trees/layer_tree_host_single_thread_client.h @@ -5,7 +5,9 @@ #ifndef CC_TREES_LAYER_TREE_HOST_SINGLE_THREAD_CLIENT_H_ #define CC_TREES_LAYER_TREE_HOST_SINGLE_THREAD_CLIENT_H_ +#include "base/containers/flat_set.h" #include "base/time/time.h" +#include "components/viz/common/surfaces/frame_sink_id.h" namespace cc { @@ -31,6 +33,12 @@ class LayerTreeHostSingleThreadClient { // run the machinery to acquire a new LayerTreeFrameSink. virtual void DidLoseLayerTreeFrameSink() = 0; + // When compositing-based throttling is enabled, this function is called every + // time when a frame composition change has updated the frame sinks to + // throttle. + virtual void FrameSinksToThrottleUpdated( + const base::flat_set& ids) {} + protected: virtual ~LayerTreeHostSingleThreadClient() {} }; diff --git a/cc/trees/proxy_impl.cc b/cc/trees/proxy_impl.cc index 37647c2896143d..a67c14b6b646ce 100644 --- a/cc/trees/proxy_impl.cc +++ b/cc/trees/proxy_impl.cc @@ -251,6 +251,11 @@ bool ProxyImpl::IsInSynchronousComposite() const { return false; } +void ProxyImpl::FrameSinksToThrottleUpdated( + const base::flat_set& ids) { + NOTREACHED(); +} + void ProxyImpl::NotifyReadyToCommitOnImpl( CompletionEvent* completion, LayerTreeHost* layer_tree_host, diff --git a/cc/trees/proxy_impl.h b/cc/trees/proxy_impl.h index ecc8342b5ef242..05eb84ed3ebdd7 100644 --- a/cc/trees/proxy_impl.h +++ b/cc/trees/proxy_impl.h @@ -128,6 +128,9 @@ class CC_EXPORT ProxyImpl : public LayerTreeHostImplClient, void DidObserveFirstScrollDelay( base::TimeDelta first_scroll_delay, base::TimeTicks first_scroll_timestamp) override; + bool IsInSynchronousComposite() const override; + void FrameSinksToThrottleUpdated( + const base::flat_set& id) override; // SchedulerClient implementation bool WillBeginImplFrame(const viz::BeginFrameArgs& args) override; @@ -151,7 +154,6 @@ class CC_EXPORT ProxyImpl : public LayerTreeHostImplClient, base::TimeTicks time) override; void FrameIntervalUpdated(base::TimeDelta interval) override {} bool HasCustomPropertyAnimations() const override; - bool IsInSynchronousComposite() const override; DrawResult DrawInternal(bool forced_draw); diff --git a/cc/trees/single_thread_proxy.cc b/cc/trees/single_thread_proxy.cc index 8ddeafb4d7b627..628454e3b90810 100644 --- a/cc/trees/single_thread_proxy.cc +++ b/cc/trees/single_thread_proxy.cc @@ -567,6 +567,16 @@ void SingleThreadProxy::NotifyThroughputTrackerResults( weak_factory_.GetWeakPtr(), std::move(results))); } +bool SingleThreadProxy::IsInSynchronousComposite() const { + return inside_synchronous_composite_; +} + +void SingleThreadProxy::FrameSinksToThrottleUpdated( + const base::flat_set& ids) { + DebugScopedSetMainThread main(task_runner_provider_); + single_thread_client_->FrameSinksToThrottleUpdated(ids); +} + void SingleThreadProxy::RequestBeginMainFrameNotExpected(bool new_state) { if (scheduler_on_impl_thread_) { scheduler_on_impl_thread_->SetMainThreadWantsBeginMainFrameNotExpected( @@ -574,10 +584,6 @@ void SingleThreadProxy::RequestBeginMainFrameNotExpected(bool new_state) { } } -bool SingleThreadProxy::IsInSynchronousComposite() const { - return inside_synchronous_composite_; -} - void SingleThreadProxy::CompositeImmediatelyForTest( base::TimeTicks frame_begin_time, bool raster) { diff --git a/cc/trees/single_thread_proxy.h b/cc/trees/single_thread_proxy.h index 8f42bda3d1dd66..246df6800cb28c 100644 --- a/cc/trees/single_thread_proxy.h +++ b/cc/trees/single_thread_proxy.h @@ -10,6 +10,7 @@ #include #include "base/cancelable_callback.h" +#include "base/containers/flat_set.h" #include "base/time/time.h" #include "cc/scheduler/scheduler.h" #include "cc/trees/layer_tree_host_impl.h" @@ -140,6 +141,8 @@ class CC_EXPORT SingleThreadProxy : public Proxy, Scheduler::PaintWorkletState state) override; void NotifyThroughputTrackerResults(CustomTrackerResults results) override; bool IsInSynchronousComposite() const override; + void FrameSinksToThrottleUpdated( + const base::flat_set& ids) override; void RequestNewLayerTreeFrameSink(); diff --git a/cc/trees/throttle_decider.cc b/cc/trees/throttle_decider.cc new file mode 100644 index 00000000000000..305d0dd38815f4 --- /dev/null +++ b/cc/trees/throttle_decider.cc @@ -0,0 +1,92 @@ +// 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 "cc/trees/throttle_decider.h" + +#include + +#include "components/viz/common/quads/compositor_render_pass_draw_quad.h" +#include "components/viz/common/quads/surface_draw_quad.h" +#include "components/viz/common/surfaces/surface_range.h" + +namespace cc { + +ThrottleDecider::ThrottleDecider() = default; + +ThrottleDecider::~ThrottleDecider() = default; + +void ThrottleDecider::Prepare() { + last_ids_.swap(ids_); + id_to_pass_map_.clear(); + ids_.clear(); +} + +void ThrottleDecider::ProcessRenderPass( + const viz::CompositorRenderPass& render_pass) { + bool foreground_blurred = + render_pass.filters.HasFilterOfType(FilterOperation::BLUR); + std::vector blur_backdrop_filter_bounds; + for (viz::QuadList::ConstIterator it = render_pass.quad_list.begin(); + it != render_pass.quad_list.end(); ++it) { + const viz::DrawQuad* quad = *it; + if (quad->material == viz::DrawQuad::Material::kCompositorRenderPass) { + // If the quad render pass has a blur backdrop filter without a mask, add + // the filter bounds to the bounds list. + const auto* render_pass_quad = + viz::CompositorRenderPassDrawQuad::MaterialCast(quad); + auto found = id_to_pass_map_.find(render_pass_quad->render_pass_id); + if (found == id_to_pass_map_.end()) { + // It is possible that this function is called when the render passes in + // a frame haven't been cleaned up yet. A RPDQ can possibly refer to an + // invalid render pass. + continue; + } + const auto& child_rp = *found->second; + if (child_rp.backdrop_filters.HasFilterOfType(FilterOperation::BLUR) && + render_pass_quad->resources + .ids[viz::RenderPassDrawQuadInternal::kMaskResourceIdIndex] == + viz::kInvalidResourceId) { + gfx::RectF blur_bounds(child_rp.output_rect); + if (child_rp.backdrop_filter_bounds) + blur_bounds.Intersect(child_rp.backdrop_filter_bounds->rect()); + quad->shared_quad_state->quad_to_target_transform.TransformRect( + &blur_bounds); + if (quad->shared_quad_state->is_clipped) { + blur_bounds.Intersect(gfx::RectF(quad->shared_quad_state->clip_rect)); + } + blur_backdrop_filter_bounds.push_back(blur_bounds); + } + } else if (quad->material == viz::DrawQuad::Material::kSurfaceContent) { + bool inside_backdrop_filter_bounds = false; + if (!foreground_blurred && !blur_backdrop_filter_bounds.empty()) { + gfx::RectF rect_in_target_space(quad->visible_rect); + quad->shared_quad_state->quad_to_target_transform.TransformRect( + &rect_in_target_space); + if (quad->shared_quad_state->is_clipped) { + rect_in_target_space.Intersect( + gfx::RectF(quad->shared_quad_state->clip_rect)); + } + + for (const gfx::RectF& blur_bounds : blur_backdrop_filter_bounds) { + if (blur_bounds.Contains(rect_in_target_space)) { + inside_backdrop_filter_bounds = true; + break; + } + } + } + const auto* surface_quad = viz::SurfaceDrawQuad::MaterialCast(quad); + const viz::SurfaceRange& range = surface_quad->surface_range; + DCHECK(range.IsValid()); + if (foreground_blurred || inside_backdrop_filter_bounds) + ids_.insert(range.end().frame_sink_id()); + } + } + id_to_pass_map_.emplace(render_pass.id, &render_pass); +} + +bool ThrottleDecider::HasThrottlingChanged() const { + return ids_ != last_ids_; +} + +} // namespace cc diff --git a/cc/trees/throttle_decider.h b/cc/trees/throttle_decider.h new file mode 100644 index 00000000000000..d41479a423c926 --- /dev/null +++ b/cc/trees/throttle_decider.h @@ -0,0 +1,48 @@ +// 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. + +#ifndef CC_TREES_THROTTLE_DECIDER_H_ +#define CC_TREES_THROTTLE_DECIDER_H_ + +#include "base/containers/flat_map.h" +#include "base/containers/flat_set.h" +#include "cc/cc_export.h" +#include "components/viz/common/quads/compositor_render_pass.h" +#include "components/viz/common/surfaces/frame_sink_id.h" + +namespace cc { + +// This class is used to decide if any frame sinks in a render pass list +// satisfies the compositing-based criteria to be throttled. +class CC_EXPORT ThrottleDecider { + public: + ThrottleDecider(); + ~ThrottleDecider(); + ThrottleDecider(const ThrottleDecider&) = delete; + ThrottleDecider& operator=(const ThrottleDecider&) = delete; + + // This function should be called at the beginning of each time when a render + // pass list is about to be processed. + void Prepare(); + // Go through the quads in |render_pass| and decide for each embedded surface + // in SurfaceDrawQuad if it can be throttled. This is a simple version where + // intersection calculation of surface/quad rects are confined to the render + // pass's constituent quads. + void ProcessRenderPass(const viz::CompositorRenderPass& render_pass); + bool HasThrottlingChanged() const; + const base::flat_set& ids() const { return ids_; } + + private: + base::flat_map + id_to_pass_map_; + // Ids of frame sinks that are qualified for throttling. + base::flat_set ids_; + // Ids of frame sinks that were qualified for throttling from last + // compositing. + base::flat_set last_ids_; +}; + +} // namespace cc + +#endif // CC_TREES_THROTTLE_DECIDER_H_ diff --git a/cc/trees/throttle_decider_unittest.cc b/cc/trees/throttle_decider_unittest.cc new file mode 100644 index 00000000000000..ebb1102d1d50ac --- /dev/null +++ b/cc/trees/throttle_decider_unittest.cc @@ -0,0 +1,111 @@ +// 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 "cc/trees/throttle_decider.h" +#include "components/viz/common/quads/compositor_render_pass_draw_quad.h" +#include "components/viz/common/quads/surface_draw_quad.h" +#include "components/viz/common/surfaces/local_surface_id.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace cc { + +class ThrottleDeciderTest : public ::testing::Test { + protected: + void RunThrottleDecider(const viz::CompositorRenderPassList& render_passes) { + throttle_decider_.Prepare(); + for (auto& render_pass : render_passes) { + throttle_decider_.ProcessRenderPass(*render_pass.get()); + } + } + const base::flat_set& GetFrameSinksToThrottle() const { + return throttle_decider_.ids(); + } + ThrottleDecider throttle_decider_; +}; + +TEST_F(ThrottleDeciderTest, BackdropFilter) { + // Create two render passes. The first render pass has a blur backdrop filter. + // The second render pass has two quads: a RPDQ referencing the first render + // pass and a surface quad. + viz::CompositorRenderPassList render_passes; + render_passes.push_back(viz::CompositorRenderPass::Create()); + render_passes.push_back(viz::CompositorRenderPass::Create()); + + gfx::Rect render_pass_rect(0, 0, 100, 100); + gfx::Rect quad_rect(0, 0, 100, 100); + viz::CompositorRenderPassId id1{1u}; + viz::CompositorRenderPassId id2{2u}; + + render_passes[0]->SetNew(id1, render_pass_rect, gfx::Rect(), + gfx::Transform()); + render_passes[0]->backdrop_filters.Append( + FilterOperation::CreateBlurFilter(5.0)); + render_passes[1]->SetNew(id2, render_pass_rect, gfx::Rect(), + gfx::Transform()); + + auto* rpdq = + render_passes[1] + ->CreateAndAppendDrawQuad(); + rpdq->material = viz::DrawQuad::Material::kCompositorRenderPass; + rpdq->render_pass_id = id1; + viz::SharedQuadState sqs1; + rpdq->shared_quad_state = &sqs1; + rpdq->rect = quad_rect; + + viz::FrameSinkId frame_sink_id{10, 10}; + auto* surface_quad = + render_passes[1]->CreateAndAppendDrawQuad(); + viz::SharedQuadState sqs2; + surface_quad->shared_quad_state = &sqs2; + surface_quad->material = viz::DrawQuad::Material::kSurfaceContent; + surface_quad->surface_range = viz::SurfaceRange( + base::nullopt, + viz::SurfaceId(frame_sink_id, viz::LocalSurfaceId( + 1u, base::UnguessableToken::Create()))); + surface_quad->rect = quad_rect; + surface_quad->visible_rect = quad_rect; + + base::flat_set expected_frame_sinks{frame_sink_id}; + // The surface quad (0,0 100x100) is entirely behind the backdrop filter on + // the rpdq (0,0 100x100) so it can be throttled. + RunThrottleDecider(render_passes); + EXPECT_EQ(GetFrameSinksToThrottle(), expected_frame_sinks); + + // Put the backdrop filter within bounds (0,10 50x50). + render_passes[0]->backdrop_filter_bounds = + base::Optional(gfx::RRectF(0.0f, 10.0f, 50.0f, 50.0f, 1.0f)); + // The surface quad (0,0 100x100) is partially behind the backdrop filter on + // the rpdq (0,10 50x50) so it should not be throttled. + RunThrottleDecider(render_passes); + EXPECT_TRUE(GetFrameSinksToThrottle().empty()); + + // Transform the surface quad to (0,10 50x50). + gfx::Transform transform; + transform.Translate(0, 10); + transform.Scale(0.5f, 0.5f); + sqs2.quad_to_target_transform = transform; + // The surface quad (0,10 50x50) is entirely behind the backdrop filter on the + // rpdq (0,10 50x50) so it can be throttled. + RunThrottleDecider(render_passes); + EXPECT_EQ(GetFrameSinksToThrottle(), expected_frame_sinks); + + // Add a mask to the backdrop filter. + rpdq->resources.ids[viz::RenderPassDrawQuadInternal::kMaskResourceIdIndex] = + viz::ResourceId::FromUnsafeValue(1u); + + // As the mask would make the backdrop filter to be ignored, the surface + // should not be throttled. + RunThrottleDecider(render_passes); + EXPECT_TRUE(GetFrameSinksToThrottle().empty()); + + // Add a foreground filter to the second render pass. + render_passes[1]->filters.Append(FilterOperation::CreateBlurFilter(5.0f)); + // The surface quad is being blurred by the foreground filter so it can be + // throttled. + RunThrottleDecider(render_passes); + EXPECT_EQ(GetFrameSinksToThrottle(), expected_frame_sinks); +} + +} // namespace cc diff --git a/components/viz/host/host_frame_sink_manager.cc b/components/viz/host/host_frame_sink_manager.cc index 5652a20dfadbb5..54dccff341bac0 100644 --- a/components/viz/host/host_frame_sink_manager.cc +++ b/components/viz/host/host_frame_sink_manager.cc @@ -306,6 +306,11 @@ void HostFrameSinkManager::EndThrottling() { frame_sink_manager_->EndThrottling(); } +void HostFrameSinkManager::Throttle(const std::vector& ids, + base::TimeDelta interval) { + frame_sink_manager_->Throttle(ids, interval); +} + 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 2c4ada609862e6..ab10f10dc33616 100644 --- a/components/viz/host/host_frame_sink_manager.h +++ b/components/viz/host/host_frame_sink_manager.h @@ -183,6 +183,8 @@ class VIZ_HOST_EXPORT HostFrameSinkManager // Ends throttling of all previously throttled frame sinks. void EndThrottling(); + void Throttle(const std::vector& ids, base::TimeDelta interval); + // 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 857443809f3f96..df78d2c3b4e017 100644 --- a/components/viz/host/host_frame_sink_manager_unittest.cc +++ b/components/viz/host/host_frame_sink_manager_unittest.cc @@ -100,6 +100,10 @@ class MockFrameSinkManagerImpl : public TestFrameSinkManagerImpl { base::TimeDelta interval), (override)); MOCK_METHOD(void, EndThrottling, (), (override)); + MOCK_METHOD(void, + Throttle, + (const std::vector& ids, base::TimeDelta interval), + (override)); }; } // namespace @@ -495,6 +499,9 @@ TEST_F(HostFrameSinkManagerTest, ThrottleFramePainting) { host().StartThrottling(frame_sink_ids, interval); EXPECT_CALL(impl(), EndThrottling()).Times(1); host().EndThrottling(); + + EXPECT_CALL(impl(), Throttle(frame_sink_ids, interval)); + host().Throttle(frame_sink_ids, interval); FlushHostAndVerifyExpectations(); } } // 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 0886b9f4d46662..9dc2ee24c8df89 100644 --- a/components/viz/service/frame_sinks/frame_sink_manager_impl.cc +++ b/components/viz/service/frame_sinks/frame_sink_manager_impl.cc @@ -664,4 +664,17 @@ void FrameSinkManagerImpl::EndThrottling() { } frame_sinks_throttled_ = false; } + +void FrameSinkManagerImpl::Throttle(const std::vector& ids, + base::TimeDelta interval) { + for (auto& support_map_item : support_map_) { + support_map_item.second->ThrottleBeginFrame(base::TimeDelta()); + } + + // Set the |interval| for frame sinks whose ids are listed in |ids|. + for (const auto& id : ids) { + UpdateThrottlingRecursively(id, interval); + } +} + } // 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 d1bcdb9d4d76f4..04013e0b70a268 100644 --- a/components/viz/service/frame_sinks/frame_sink_manager_impl.h +++ b/components/viz/service/frame_sinks/frame_sink_manager_impl.h @@ -142,6 +142,8 @@ class VIZ_SERVICE_EXPORT FrameSinkManagerImpl void StartThrottling(const std::vector& frame_sink_ids, base::TimeDelta interval) override; void EndThrottling() override; + void Throttle(const std::vector& ids, + base::TimeDelta interval) override; // SurfaceObserver implementation. void OnFirstSurfaceActivation(const SurfaceInfo& surface_info) override; 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 3944282e9f3d45..e5e0fa9bd3dec1 100644 --- a/components/viz/service/frame_sinks/frame_sink_manager_unittest.cc +++ b/components/viz/service/frame_sinks/frame_sink_manager_unittest.cc @@ -468,6 +468,75 @@ TEST_F(FrameSinkManagerTest, ThrottleBeginFrame) { client_d->frame_sink_id()); } +// Verifies the the begin frames are throttled properly for the requested frame +// sinks and their children. +TEST_F(FrameSinkManagerTest, Throttle) { + // 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::FromHz(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_.Throttle({kFrameSinkIdRoot}, interval); + for (auto& id : ids) { + EXPECT_EQ(GetCompositorFrameSinkSupportBeginFrameInterval(id), interval); + } + + manager_.Throttle({}, base::TimeDelta()); + for (auto& id : ids) { + EXPECT_EQ(GetCompositorFrameSinkSupportBeginFrameInterval(id), + base::TimeDelta()); + } + + manager_.Throttle({kFrameSinkIdB, kFrameSinkIdC}, interval); + for (auto& id : {kFrameSinkIdB, kFrameSinkIdC, kFrameSinkIdD}) { + EXPECT_EQ(GetCompositorFrameSinkSupportBeginFrameInterval(id), interval); + } + for (auto& id : {kFrameSinkIdA, kFrameSinkIdRoot}) { + EXPECT_EQ(GetCompositorFrameSinkSupportBeginFrameInterval(id), + base::TimeDelta()); + } + + manager_.Throttle({}, base::TimeDelta()); + 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 ca02f1e33d3cff..38f5e95e137ab6 100644 --- a/components/viz/test/test_frame_sink_manager.h +++ b/components/viz/test/test_frame_sink_manager.h @@ -5,6 +5,10 @@ #ifndef COMPONENTS_VIZ_TEST_TEST_FRAME_SINK_MANAGER_H_ #define COMPONENTS_VIZ_TEST_TEST_FRAME_SINK_MANAGER_H_ +#include +#include +#include + #include "base/macros.h" #include "mojo/public/cpp/bindings/pending_receiver.h" #include "mojo/public/cpp/bindings/pending_remote.h" @@ -65,6 +69,8 @@ class TestFrameSinkManagerImpl : public mojom::FrameSinkManager { void StartThrottling(const std::vector& frame_sink_ids, base::TimeDelta interval) override {} void EndThrottling() override {} + void Throttle(const std::vector& ids, + base::TimeDelta interval) 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 a15445e30fcb7e..427017cc9c38cb 100644 --- a/services/viz/privileged/mojom/compositing/frame_sink_manager.mojom +++ b/services/viz/privileged/mojom/compositing/frame_sink_manager.mojom @@ -136,6 +136,12 @@ interface FrameSinkManager { // Ends all previously throttled frame sinks. EndThrottling(); + // Throttles the frame sinks specified by |frame_sink_ids| and all their + // descendant sinks to send BeginFrames at an interval of |interval|. This + // operation clears out any previous throttling operation on any frame sinks. + Throttle(array frame_sink_ids, + mojo_base.mojom.TimeDelta interval); + // 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/ui/aura/window_tree_host.cc b/ui/aura/window_tree_host.cc index 6fb3226c3423a7..6faeee8d7ebea8 100644 --- a/ui/aura/window_tree_host.cc +++ b/ui/aura/window_tree_host.cc @@ -559,4 +559,10 @@ void WindowTreeHost::OnCompositingChildResizing(ui::Compositor* compositor) { holding_pointer_moves_ = true; } +void WindowTreeHost::OnFrameSinksToThrottleUpdated( + const base::flat_set& ids) { + for (auto& observer : observers_) + observer.OnCompositingFrameSinksToThrottleUpdated(this, ids); +} + } // namespace aura diff --git a/ui/aura/window_tree_host.h b/ui/aura/window_tree_host.h index 93b50524e3dfcc..93c56f7ffbc9e2 100644 --- a/ui/aura/window_tree_host.h +++ b/ui/aura/window_tree_host.h @@ -334,9 +334,11 @@ class AURA_EXPORT WindowTreeHost : public ui::internal::InputMethodDelegate, void MoveCursorToInternal(const gfx::Point& root_location, const gfx::Point& host_location); - // Overrided from CompositorObserver: - void OnCompositingEnded(ui::Compositor* compositor) override; - void OnCompositingChildResizing(ui::Compositor* compositor) override; + // Overridden from CompositorObserver: + void OnCompositingEnded(ui::Compositor* compositor) final; + void OnCompositingChildResizing(ui::Compositor* compositor) final; + void OnFrameSinksToThrottleUpdated( + const base::flat_set& ids) final; // We don't use a std::unique_ptr for |window_| since we need this ptr to be // valid during its deletion. (Window's dtor notifies observers that may diff --git a/ui/aura/window_tree_host_observer.h b/ui/aura/window_tree_host_observer.h index 5b8da3b28da47d..995ada6aa5d53f 100644 --- a/ui/aura/window_tree_host_observer.h +++ b/ui/aura/window_tree_host_observer.h @@ -5,6 +5,8 @@ #ifndef UI_AURA_WINDOW_TREE_HOST_OBSERVER_H_ #define UI_AURA_WINDOW_TREE_HOST_OBSERVER_H_ +#include "base/containers/flat_set.h" +#include "components/viz/common/surfaces/frame_sink_id.h" #include "ui/aura/aura_export.h" #include "ui/aura/window.h" @@ -43,6 +45,10 @@ class AURA_EXPORT WindowTreeHostObserver { virtual void OnHostWillProcessBoundsChange(WindowTreeHost* host) {} virtual void OnHostDidProcessBoundsChange(WindowTreeHost* host) {} + virtual void OnCompositingFrameSinksToThrottleUpdated( + const aura::WindowTreeHost* host, + const base::flat_set& ids) {} + protected: virtual ~WindowTreeHostObserver() {} }; diff --git a/ui/compositor/compositor.cc b/ui/compositor/compositor.cc index c70bc2bb16c551..54d981d9a6f129 100644 --- a/ui/compositor/compositor.cc +++ b/ui/compositor/compositor.cc @@ -723,6 +723,13 @@ void Compositor::FrameIntervalUpdated(base::TimeDelta interval) { refresh_rate_ = interval.ToHz(); } +void Compositor::FrameSinksToThrottleUpdated( + const base::flat_set& ids) { + for (auto& observer : observer_list_) { + observer.OnFrameSinksToThrottleUpdated(ids); + } +} + void Compositor::OnFirstSurfaceActivation( const viz::SurfaceInfo& surface_info) { NOTREACHED(); diff --git a/ui/compositor/compositor.h b/ui/compositor/compositor.h index 4dd2ed45df05dd..e4ff4cb0a2ec2b 100644 --- a/ui/compositor/compositor.h +++ b/ui/compositor/compositor.h @@ -12,6 +12,7 @@ #include "base/callback_forward.h" #include "base/containers/flat_map.h" +#include "base/containers/flat_set.h" #include "base/macros.h" #include "base/memory/ref_counted.h" #include "base/observer_list.h" @@ -80,7 +81,7 @@ class ContextProvider; class HostFrameSinkManager; class LocalSurfaceId; class RasterContextProvider; -} +} // namespace viz namespace ui { class Compositor; @@ -357,6 +358,8 @@ class COMPOSITOR_EXPORT Compositor : public cc::LayerTreeHostClient, void DidSubmitCompositorFrame() override; void DidLoseLayerTreeFrameSink() override {} void FrameIntervalUpdated(base::TimeDelta interval) override; + void FrameSinksToThrottleUpdated( + const base::flat_set& ids) override; // viz::HostFrameSinkClient implementation. void OnFirstSurfaceActivation(const viz::SurfaceInfo& surface_info) override; diff --git a/ui/compositor/compositor_observer.h b/ui/compositor/compositor_observer.h index a14b75b5df7488..afef8731de30e5 100644 --- a/ui/compositor/compositor_observer.h +++ b/ui/compositor/compositor_observer.h @@ -5,9 +5,11 @@ #ifndef UI_COMPOSITOR_COMPOSITOR_OBSERVER_H_ #define UI_COMPOSITOR_COMPOSITOR_OBSERVER_H_ +#include "base/containers/flat_set.h" #include "base/time/time.h" #include "build/build_config.h" #include "build/chromeos_buildflags.h" +#include "components/viz/common/surfaces/frame_sink_id.h" #include "ui/compositor/compositor_export.h" namespace gfx { @@ -63,6 +65,9 @@ class COMPOSITOR_EXPORT CompositorObserver { virtual void OnFirstAnimationStarted(Compositor* compositor) {} virtual void OnLastAnimationEnded(Compositor* compositor) {} + + virtual void OnFrameSinksToThrottleUpdated( + const base::flat_set& ids) {} }; } // namespace ui