diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-12 14:27:29 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-13 09:35:20 +0000 |
commit | c30a6232df03e1efbd9f3b226777b07e087a1122 (patch) | |
tree | e992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/cc | |
parent | 7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff) | |
download | qtwebengine-chromium-85-based.tar.gz |
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/cc')
217 files changed, 6476 insertions, 3216 deletions
diff --git a/chromium/cc/BUILD.gn b/chromium/cc/BUILD.gn index deaca4f894b..7e5e837b8e7 100644 --- a/chromium/cc/BUILD.gn +++ b/chromium/cc/BUILD.gn @@ -32,7 +32,6 @@ cc_component("cc") { "input/browser_controls_offset_manager.cc", "input/browser_controls_offset_manager.h", "input/browser_controls_offset_manager_client.h", - "input/input_handler.cc", "input/input_handler.h", "input/layer_selection_bound.cc", "input/layer_selection_bound.h", @@ -49,6 +48,9 @@ cc_component("cc") { "input/scroll_state.h", "input/scroll_state_data.cc", "input/scroll_state_data.h", + "input/scroll_utils.cc", + "input/scroll_utils.h", + "input/scrollbar.h", "input/scrollbar_animation_controller.cc", "input/scrollbar_animation_controller.h", "input/scrollbar_controller.cc", @@ -101,8 +103,6 @@ cc_component("cc") { "layers/painted_scrollbar_layer.h", "layers/painted_scrollbar_layer_impl.cc", "layers/painted_scrollbar_layer_impl.h", - "layers/picture_image_layer.cc", - "layers/picture_image_layer.h", "layers/picture_layer.cc", "layers/picture_layer.h", "layers/picture_layer_impl.cc", @@ -157,6 +157,8 @@ cc_component("cc") { "metrics/compositor_frame_reporting_controller.h", "metrics/compositor_timing_history.cc", "metrics/compositor_timing_history.h", + "metrics/dropped_frame_counter.cc", + "metrics/dropped_frame_counter.h", "metrics/event_metrics.cc", "metrics/event_metrics.h", "metrics/events_metrics_manager.cc", @@ -312,8 +314,6 @@ cc_component("cc") { "trees/draw_property_utils.h", "trees/effect_node.cc", "trees/effect_node.h", - "trees/frame_rate_counter.cc", - "trees/frame_rate_counter.h", "trees/frame_rate_estimator.cc", "trees/frame_rate_estimator.h", "trees/image_animation_controller.cc", @@ -642,7 +642,6 @@ cc_test("cc_unittests") { "layers/painted_overlay_scrollbar_layer_unittest.cc", "layers/painted_scrollbar_layer_impl_unittest.cc", "layers/painted_scrollbar_layer_unittest.cc", - "layers/picture_image_layer_unittest.cc", "layers/picture_layer_impl_unittest.cc", "layers/picture_layer_unittest.cc", "layers/recording_source_unittest.cc", diff --git a/chromium/cc/animation/animation.cc b/chromium/cc/animation/animation.cc index 5c40eed8453..df27dc32d11 100644 --- a/chromium/cc/animation/animation.cc +++ b/chromium/cc/animation/animation.cc @@ -127,20 +127,15 @@ void Animation::Tick(base::TimeTicks tick_time) { // time and then ticks it which side-steps the start time altogether. See // crbug.com/1076012 for alternative design choices considered for future // improvement. - TickWithLocalTime(tick_time - base::TimeTicks()); + keyframe_effect_->Pause(tick_time - base::TimeTicks(), + PauseCondition::kAfterStart); + keyframe_effect_->Tick(base::TimeTicks()); } else { DCHECK(!tick_time.is_null()); keyframe_effect_->Tick(tick_time); } } -void Animation::TickWithLocalTime(base::TimeDelta local_time) { - // TODO(yigu): KeyframeEffect should support ticking KeyframeModel - // directly without using Pause(). https://crbug.com/1076012. - keyframe_effect_->Pause(local_time); - keyframe_effect_->Tick(base::TimeTicks()); -} - bool Animation::IsScrollLinkedAnimation() const { return animation_timeline_ && animation_timeline_->IsScrollTimeline(); } @@ -211,10 +206,6 @@ void Animation::DelegateAnimationEvent(const AnimationEvent& event) { } } -size_t Animation::TickingKeyframeModelsCount() const { - return keyframe_effect_->TickingKeyframeModelsCount(); -} - bool Animation::AffectsCustomProperty() const { return keyframe_effect_->AffectsCustomProperty(); } @@ -287,12 +278,4 @@ void Animation::NotifyKeyframeModelFinishedForTesting( DispatchAndDelegateAnimationEvent(event); } -void Animation::UpdateScrollTimeline(base::Optional<ElementId> scroller_id, - base::Optional<double> start_scroll_offset, - base::Optional<double> end_scroll_offset) { - ToScrollTimeline(animation_timeline_) - ->UpdateScrollerIdAndScrollOffsets(scroller_id, start_scroll_offset, - end_scroll_offset); -} - } // namespace cc diff --git a/chromium/cc/animation/animation.h b/chromium/cc/animation/animation.h index 7fa4136d591..6cc50117d1f 100644 --- a/chromium/cc/animation/animation.h +++ b/chromium/cc/animation/animation.h @@ -65,17 +65,6 @@ class CC_ANIMATION_EXPORT Animation : public base::RefCounted<Animation> { } void SetAnimationTimeline(AnimationTimeline* timeline); - // TODO(yigu): There is a reverse dependency between AnimationTimeline and - // Animation. ScrollTimeline update should be handled by AnimationHost instead - // of Animation. This could be fixed once the snapshotting in blink is - // implemented. https://crbug.com/1023508. - - // Should be called when the ScrollTimeline attached to this animation has a - // change, such as when the scroll source changes ElementId. - void UpdateScrollTimeline(base::Optional<ElementId> scroller_id, - base::Optional<double> start_scroll_offset, - base::Optional<double> end_scroll_offset); - scoped_refptr<ElementAnimations> element_animations() const; void set_animation_delegate(AnimationDelegate* delegate) { @@ -118,7 +107,6 @@ class CC_ANIMATION_EXPORT Animation : public base::RefCounted<Animation> { // to be dispatched. void DispatchAndDelegateAnimationEvent(const AnimationEvent& event); - size_t TickingKeyframeModelsCount() const; bool AffectsCustomProperty() const; void SetNeedsPushProperties(); @@ -151,7 +139,6 @@ class CC_ANIMATION_EXPORT Animation : public base::RefCounted<Animation> { explicit Animation(int id); Animation(int id, std::unique_ptr<KeyframeEffect>); virtual ~Animation(); - void TickWithLocalTime(base::TimeDelta local_time); AnimationHost* animation_host_; AnimationTimeline* animation_timeline_; diff --git a/chromium/cc/animation/animation_delegate.h b/chromium/cc/animation/animation_delegate.h index 0adbb115c59..6931c8b0d18 100644 --- a/chromium/cc/animation/animation_delegate.h +++ b/chromium/cc/animation/animation_delegate.h @@ -13,12 +13,6 @@ namespace cc { class CC_ANIMATION_EXPORT AnimationDelegate { public: - // TODO(yigu): The Notify* methods will be called multiple times per - // animation (once for effect/property pairing). - // Ideally, we would only notify start once (e.g., wait on all effects to - // start before notifying delegate) this way effect becomes an internal - // details of the animation. Perhaps we can do that at some point maybe as - // part of https://bugs.chromium.org/p/chromium/issues/detail?id=810003 virtual void NotifyAnimationStarted(base::TimeTicks monotonic_time, int target_property, int group) = 0; diff --git a/chromium/cc/animation/animation_host.cc b/chromium/cc/animation/animation_host.cc index 1dfe39e66c9..d2154f19594 100644 --- a/chromium/cc/animation/animation_host.cc +++ b/chromium/cc/animation/animation_host.cc @@ -219,6 +219,8 @@ void AnimationHost::SetNeedsCommit() { } void AnimationHost::SetNeedsPushProperties() { + if (needs_push_properties_) + return; needs_push_properties_ = true; if (mutator_host_client_) mutator_host_client_->SetMutatorsNeedCommit(); @@ -227,6 +229,14 @@ void AnimationHost::SetNeedsPushProperties() { void AnimationHost::PushPropertiesTo(MutatorHost* mutator_host_impl) { auto* host_impl = static_cast<AnimationHost*>(mutator_host_impl); + // Update animation counts and whether raf was requested. These explicitly + // do not request push properties and are pushed as part of the next commit + // when it happens as requesting a commit leads to performance issues: + // https://crbug.com/1083244 + host_impl->main_thread_animations_count_ = main_thread_animations_count_; + host_impl->current_frame_had_raf_ = current_frame_had_raf_; + host_impl->next_frame_has_pending_raf_ = next_frame_has_pending_raf_; + if (needs_push_properties_) { needs_push_properties_ = false; PushTimelinesToImplThread(host_impl); @@ -289,9 +299,6 @@ void AnimationHost::PushPropertiesToImplThread(AnimationHost* host_impl) { // Update the impl-only scroll offset animations. scroll_offset_animations_->PushPropertiesTo( host_impl->scroll_offset_animations_impl_.get()); - host_impl->main_thread_animations_count_ = main_thread_animations_count_; - host_impl->current_frame_had_raf_ = current_frame_had_raf_; - host_impl->next_frame_has_pending_raf_ = next_frame_has_pending_raf_; // The pending info list is cleared in LayerTreeHostImpl::CommitComplete // and should be empty when pushing properties. @@ -752,37 +759,25 @@ void AnimationHost::SetMutationUpdate( } } -size_t AnimationHost::CompositedAnimationsCount() const { - size_t composited_animations_count = 0; - for (const auto& it : ticking_animations_) - composited_animations_count += it->TickingKeyframeModelsCount(); - return composited_animations_count; -} - void AnimationHost::SetAnimationCounts( size_t total_animations_count, bool current_frame_had_raf, bool next_frame_has_pending_raf) { + // Though these changes are pushed as part of AnimationHost::PushPropertiesTo + // we don't SetNeedsPushProperties as pushing the values requires a commit. + // Instead we allow them to be pushed whenever the next required commit + // happens to avoid unnecessary work. See https://crbug.com/1083244. + // If an animation is being run on the compositor, it will have a ticking // Animation (which will have a corresponding impl-thread version). Therefore // to find the count of main-only animations, we can simply subtract the // number of ticking animations from the total count. size_t ticking_animations_count = ticking_animations_.size(); - if (main_thread_animations_count_ != - total_animations_count - ticking_animations_count) { - main_thread_animations_count_ = - total_animations_count - ticking_animations_count; - DCHECK_GE(main_thread_animations_count_, 0u); - SetNeedsPushProperties(); - } - if (current_frame_had_raf != current_frame_had_raf_) { - current_frame_had_raf_ = current_frame_had_raf; - SetNeedsPushProperties(); - } - if (next_frame_has_pending_raf != next_frame_has_pending_raf_) { - next_frame_has_pending_raf_ = next_frame_has_pending_raf; - SetNeedsPushProperties(); - } + main_thread_animations_count_ = + total_animations_count - ticking_animations_count; + DCHECK_GE(main_thread_animations_count_, 0u); + current_frame_had_raf_ = current_frame_had_raf; + next_frame_has_pending_raf_ = next_frame_has_pending_raf; } size_t AnimationHost::MainThreadAnimationsCount() const { diff --git a/chromium/cc/animation/animation_host.h b/chromium/cc/animation/animation_host.h index de1ae6f8811..534f8c0c642 100644 --- a/chromium/cc/animation/animation_host.h +++ b/chromium/cc/animation/animation_host.h @@ -207,7 +207,6 @@ class CC_ANIMATION_EXPORT AnimationHost : public MutatorHost, void SetMutationUpdate( std::unique_ptr<MutatorOutputState> output_state) override; - size_t CompositedAnimationsCount() const override; size_t MainThreadAnimationsCount() const override; bool HasCustomPropertyAnimations() const override; bool CurrentFrameHadRAF() const override; diff --git a/chromium/cc/animation/animation_host_unittest.cc b/chromium/cc/animation/animation_host_unittest.cc index c2659983070..4dc4f9905d5 100644 --- a/chromium/cc/animation/animation_host_unittest.cc +++ b/chromium/cc/animation/animation_host_unittest.cc @@ -363,6 +363,9 @@ TEST_F(AnimationHostTest, LayerTreeMutatorUpdateReflectsScrollAnimations) { } TEST_F(AnimationHostTest, TickScrollLinkedAnimation) { + client_.RegisterElementId(element_id_, ElementListType::ACTIVE); + client_impl_.RegisterElementId(element_id_, ElementListType::PENDING); + client_impl_.RegisterElementId(element_id_, ElementListType::ACTIVE); PropertyTrees property_trees; property_trees.is_main_thread = false; property_trees.is_active = true; @@ -378,7 +381,7 @@ TEST_F(AnimationHostTest, TickScrollLinkedAnimation) { scoped_refptr<Animation> animation = Animation::Create(animation_id); host_impl_->AddAnimationTimeline(scroll_timeline); scroll_timeline->AttachAnimation(animation); - animation->AddToTicking(); + ASSERT_TRUE(animation->IsScrollLinkedAnimation()); animation->AttachElement(element_id_); @@ -393,7 +396,7 @@ TEST_F(AnimationHostTest, TickScrollLinkedAnimation) { EXPECT_TRUE(host_impl_->TickAnimations(base::TimeTicks(), property_trees.scroll_tree, false)); - EXPECT_EQ(keyframe_model->run_state(), KeyframeModel::PAUSED); + EXPECT_EQ(keyframe_model->run_state(), KeyframeModel::STARTING); double tick_time = (scroll_timeline->CurrentTime(scroll_tree, false).value() - base::TimeTicks()) .InSecondsF(); diff --git a/chromium/cc/animation/element_animations_unittest.cc b/chromium/cc/animation/element_animations_unittest.cc index 34debd1808a..9e1fa8337f3 100644 --- a/chromium/cc/animation/element_animations_unittest.cc +++ b/chromium/cc/animation/element_animations_unittest.cc @@ -3874,26 +3874,6 @@ TEST_F(ElementAnimationsTest, RemoveAndReAddAnimationToTicking) { EXPECT_EQ(1u, host_->ticking_animations_for_testing().size()); } -TEST_F(ElementAnimationsTest, TickingKeyframeModelsCount) { - CreateTestLayer(false, false); - AttachTimelineAnimationLayer(); - - // Add an animation and ensure the animation is in the host's ticking - // animations. - animation_->AddKeyframeModel(CreateKeyframeModel( - std::unique_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 1.f, 0.5f)), - 2, TargetProperty::OPACITY)); - EXPECT_EQ(1u, animation_->TickingKeyframeModelsCount()); - EXPECT_EQ(1u, host_->CompositedAnimationsCount()); - animation_->AddKeyframeModel(CreateKeyframeModel( - std::unique_ptr<AnimationCurve>(new FakeTransformTransition(1)), 1, - TargetProperty::TRANSFORM)); - EXPECT_EQ(2u, animation_->TickingKeyframeModelsCount()); - EXPECT_EQ(2u, host_->CompositedAnimationsCount()); - animation_->keyframe_effect()->RemoveFromTicking(); - EXPECT_EQ(0u, host_->CompositedAnimationsCount()); -} - // This test verifies that finished keyframe models don't get copied over to // impl thread. TEST_F(ElementAnimationsTest, FinishedKeyframeModelsNotCopiedToImpl) { diff --git a/chromium/cc/animation/keyframe_effect.cc b/chromium/cc/animation/keyframe_effect.cc index 5af52eb211b..710d4f51c13 100644 --- a/chromium/cc/animation/keyframe_effect.cc +++ b/chromium/cc/animation/keyframe_effect.cc @@ -214,14 +214,28 @@ void KeyframeEffect::UpdateTickingState() { } } -void KeyframeEffect::Pause(base::TimeDelta pause_offset) { - for (auto& keyframe_model : keyframe_models_) +void KeyframeEffect::Pause(base::TimeDelta pause_offset, + PauseCondition pause_condition) { + bool did_pause = false; + for (auto& keyframe_model : keyframe_models_) { + // TODO(crbug.com/1076012): KeyframeEffect is paused with local time for + // scroll-linked animations. To make sure the start event of a keyframe + // model is sent to blink, we should not set its run state to PAUSED until + // such event is sent. This should be revisited once KeyframeEffect is able + // to tick scroll-linked keyframe models directly. + if (pause_condition == PauseCondition::kAfterStart && + (keyframe_model->run_state() == + KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY || + keyframe_model->run_state() == KeyframeModel::STARTING)) + continue; keyframe_model->Pause(pause_offset); - - if (has_bound_element_animations()) { - animation_->SetNeedsCommit(); - SetNeedsPushProperties(); + did_pause = true; } + + if (!did_pause || !has_bound_element_animations()) + return; + animation_->SetNeedsCommit(); + SetNeedsPushProperties(); } void KeyframeEffect::AddKeyframeModel( @@ -458,14 +472,6 @@ bool KeyframeEffect::HasTickingKeyframeModel() const { return false; } -size_t KeyframeEffect::TickingKeyframeModelsCount() const { - size_t ticking_keyframe_models_count = 0; - for (const auto& it : keyframe_models_) - if (!it->is_finished()) - ticking_keyframe_models_count++; - return ticking_keyframe_models_count; -} - bool KeyframeEffect::AffectsCustomProperty() const { for (const auto& it : keyframe_models_) if (it->target_property_id() == TargetProperty::CSS_CUSTOM_PROPERTY) diff --git a/chromium/cc/animation/keyframe_effect.h b/chromium/cc/animation/keyframe_effect.h index e7806e4cc1d..0e1280f82e1 100644 --- a/chromium/cc/animation/keyframe_effect.h +++ b/chromium/cc/animation/keyframe_effect.h @@ -22,6 +22,7 @@ namespace cc { class Animation; +enum class PauseCondition { kUnconditional, kAfterStart }; struct PropertyAnimationState; // A KeyframeEffect owns a group of KeyframeModels for a single target @@ -86,7 +87,8 @@ class CC_ANIMATION_EXPORT KeyframeEffect { void UpdateState(bool start_ready_keyframe_models, AnimationEvents* events); void UpdateTickingState(); - void Pause(base::TimeDelta pause_offset); + void Pause(base::TimeDelta pause_offset, + PauseCondition = PauseCondition::kUnconditional); void AddKeyframeModel(std::unique_ptr<KeyframeModel> keyframe_model); void PauseKeyframeModel(int keyframe_model_id, base::TimeDelta time_offset); @@ -106,7 +108,6 @@ class CC_ANIMATION_EXPORT KeyframeEffect { // Returns true if there are any KeyframeModels that have neither finished // nor aborted. bool HasTickingKeyframeModel() const; - size_t TickingKeyframeModelsCount() const; bool AffectsCustomProperty() const; diff --git a/chromium/cc/animation/keyframe_model.cc b/chromium/cc/animation/keyframe_model.cc index 25e2c6d232b..bb779226080 100644 --- a/chromium/cc/animation/keyframe_model.cc +++ b/chromium/cc/animation/keyframe_model.cc @@ -156,7 +156,7 @@ void KeyframeModel::SetRunState(RunState run_state, void KeyframeModel::Pause(base::TimeDelta pause_offset) { // Convert pause offset which is in local time to monotonic time. - // TODO(yigu): This should be scaled by playbackrate. http://crbug.com/912407 + // TODO(crbug.com/912407): This should be scaled by playbackrate. base::TimeTicks monotonic_time = pause_offset + start_time_ + total_paused_duration_; SetRunState(PAUSED, monotonic_time); @@ -246,8 +246,7 @@ bool KeyframeModel::InEffect(base::TimeTicks monotonic_time) const { return CalculateActiveTime(monotonic_time).has_value(); } -// TODO(yigu): Local time should be scaled by playback rate by spec. -// https://crbug.com/912407. +// TODO(crbug.com/912407): Local time should be scaled by playback rate by spec. base::TimeDelta KeyframeModel::ConvertMonotonicTimeToLocalTime( base::TimeTicks monotonic_time) const { // When waiting on receiving a start time, then our global clock is 'stuck' at diff --git a/chromium/cc/animation/transform_operations.h b/chromium/cc/animation/transform_operations.h index 2fecd424325..f0c669cf3e7 100644 --- a/chromium/cc/animation/transform_operations.h +++ b/chromium/cc/animation/transform_operations.h @@ -9,8 +9,8 @@ #include <unordered_map> #include <vector> +#include "base/check_op.h" #include "base/gtest_prod_util.h" -#include "base/logging.h" #include "cc/animation/animation_export.h" #include "cc/animation/transform_operation.h" #include "ui/gfx/transform.h" diff --git a/chromium/cc/animation/worklet_animation.cc b/chromium/cc/animation/worklet_animation.cc index 17ed083efcc..fd9a9c700ea 100644 --- a/chromium/cc/animation/worklet_animation.cc +++ b/chromium/cc/animation/worklet_animation.cc @@ -92,7 +92,8 @@ void WorkletAnimation::Tick(base::TimeTicks monotonic_time) { // animations lifecycle. To avoid this we pause the underlying keyframe effect // at the local time obtained from the user script - essentially turning each // call to |WorkletAnimation::Tick| into a seek in the effect. - TickWithLocalTime(local_time_.value()); + keyframe_effect_->Pause(local_time_.value()); + keyframe_effect_->Tick(base::TimeTicks()); } void WorkletAnimation::UpdateState(bool start_ready_animations, @@ -162,10 +163,6 @@ void WorkletAnimation::UpdateInputState(MutatorInputState* input_state, switch (state_) { case State::PENDING: - // TODO(yigu): cc side WorkletAnimation is only capable of handling single - // keyframe effect at the moment. We should pass in the number of effects - // once Worklet Group Effect is fully implemented in cc. - // https://crbug.com/767043. input_state->Add({worklet_animation_id(), name(), current_time->InMillisecondsF(), CloneOptions(), CloneEffectTimings()}); @@ -186,8 +183,6 @@ void WorkletAnimation::UpdateInputState(MutatorInputState* input_state, void WorkletAnimation::SetOutputState( const MutatorOutputState::AnimationState& state) { - // TODO(yigu): cc side WorkletAnimation is only capable of handling single - // keyframe effect at the moment. https://crbug.com/767043. DCHECK_EQ(state.local_times.size(), 1u); local_time_ = state.local_times[0]; } diff --git a/chromium/cc/animation/worklet_animation_unittest.cc b/chromium/cc/animation/worklet_animation_unittest.cc index e827c3740e3..ed610a56c19 100644 --- a/chromium/cc/animation/worklet_animation_unittest.cc +++ b/chromium/cc/animation/worklet_animation_unittest.cc @@ -555,20 +555,6 @@ TEST_F(WorkletAnimationTest, SkipLockedAnimations) { EXPECT_EQ(input->updated_animations.size(), 1u); } -TEST_F(WorkletAnimationTest, UpdateScrollTimelineScrollerId) { - auto scroll_timeline = base::WrapRefCounted(new MockScrollTimeline()); - EXPECT_EQ(scroll_timeline->GetPendingIdForTest(), ElementId()); - - scoped_refptr<WorkletAnimation> worklet_animation = WorkletAnimation::Create( - worklet_animation_id_, "test_name", 1, nullptr, nullptr); - host_->AddAnimationTimeline(scroll_timeline); - scroll_timeline->AttachAnimation(worklet_animation); - ElementId scroller_id = ElementId(1); - worklet_animation->UpdateScrollTimeline(scroller_id, base::nullopt, - base::nullopt); - EXPECT_EQ(scroll_timeline->GetPendingIdForTest(), scroller_id); -} - } // namespace } // namespace cc diff --git a/chromium/cc/base/completion_event.h b/chromium/cc/base/completion_event.h index 5c9debd64f4..f5084d5346c 100644 --- a/chromium/cc/base/completion_event.h +++ b/chromium/cc/base/completion_event.h @@ -5,7 +5,7 @@ #ifndef CC_BASE_COMPLETION_EVENT_H_ #define CC_BASE_COMPLETION_EVENT_H_ -#include "base/logging.h" +#include "base/check.h" #include "base/synchronization/waitable_event.h" #include "base/threading/thread_restrictions.h" #include "base/time/time.h" diff --git a/chromium/cc/base/devtools_instrumentation.cc b/chromium/cc/base/devtools_instrumentation.cc index 72b7be7a999..b386c7527c7 100644 --- a/chromium/cc/base/devtools_instrumentation.cc +++ b/chromium/cc/base/devtools_instrumentation.cc @@ -66,23 +66,34 @@ ScopedImageUploadTask::~ScopedImageUploadTask() { return; auto duration = base::TimeTicks::Now() - start_time_; + const char* histogram_name = nullptr; switch (image_type_) { - case ImageType::kWebP: - UmaHistogramCustomMicrosecondsTimes( - "Renderer4.ImageUploadTaskDurationUs.WebP", duration, hist_min_, - hist_max_, bucket_count_); + case ImageType::kAvif: + histogram_name = "Renderer4.ImageUploadTaskDurationUs.Avif"; + break; + case ImageType::kBmp: + histogram_name = "Renderer4.ImageUploadTaskDurationUs.Bmp"; + break; + case ImageType::kGif: + histogram_name = "Renderer4.ImageUploadTaskDurationUs.Gif"; + break; + case ImageType::kIco: + histogram_name = "Renderer4.ImageUploadTaskDurationUs.Ico"; break; case ImageType::kJpeg: - UmaHistogramCustomMicrosecondsTimes( - "Renderer4.ImageUploadTaskDurationUs.Jpeg", duration, hist_min_, - hist_max_, bucket_count_); + histogram_name = "Renderer4.ImageUploadTaskDurationUs.Jpeg"; break; - case ImageType::kOther: - UmaHistogramCustomMicrosecondsTimes( - "Renderer4.ImageUploadTaskDurationUs.Other", duration, hist_min_, - hist_max_, bucket_count_); + case ImageType::kPng: + histogram_name = "Renderer4.ImageUploadTaskDurationUs.Png"; break; + case ImageType::kWebP: + histogram_name = "Renderer4.ImageUploadTaskDurationUs.WebP"; + break; + case ImageType::kOther: + histogram_name = "Renderer4.ImageUploadTaskDurationUs.Other"; } + UmaHistogramCustomMicrosecondsTimes(histogram_name, duration, hist_min_, + hist_max_, bucket_count_); } ScopedImageDecodeTask::ScopedImageDecodeTask(const void* image_ptr, @@ -104,23 +115,36 @@ ScopedImageDecodeTask::~ScopedImageDecodeTask() { return; auto duration = base::TimeTicks::Now() - start_time_; + const char* histogram_name = nullptr; switch (image_type_) { - case ImageType::kWebP: - RecordMicrosecondTimesUmaByDecodeType( - "Renderer4.ImageDecodeTaskDurationUs.WebP", duration, hist_min_, - hist_max_, bucket_count_, decode_type_); + case ImageType::kAvif: + histogram_name = "Renderer4.ImageDecodeTaskDurationUs.Avif"; + break; + case ImageType::kBmp: + histogram_name = "Renderer4.ImageDecodeTaskDurationUs.Bmp"; + break; + case ImageType::kGif: + histogram_name = "Renderer4.ImageDecodeTaskDurationUs.Gif"; + break; + case ImageType::kIco: + histogram_name = "Renderer4.ImageDecodeTaskDurationUs.Ico"; break; case ImageType::kJpeg: - RecordMicrosecondTimesUmaByDecodeType( - "Renderer4.ImageDecodeTaskDurationUs.Jpeg", duration, hist_min_, - hist_max_, bucket_count_, decode_type_); + histogram_name = "Renderer4.ImageDecodeTaskDurationUs.Jpeg"; + break; + case ImageType::kPng: + histogram_name = "Renderer4.ImageDecodeTaskDurationUs.Png"; + break; + case ImageType::kWebP: + histogram_name = "Renderer4.ImageDecodeTaskDurationUs.WebP"; break; case ImageType::kOther: - RecordMicrosecondTimesUmaByDecodeType( - "Renderer4.ImageDecodeTaskDurationUs.Other", duration, hist_min_, - hist_max_, bucket_count_, decode_type_); + histogram_name = "Renderer4.ImageDecodeTaskDurationUs.Other"; break; } + RecordMicrosecondTimesUmaByDecodeType(histogram_name, duration, hist_min_, + hist_max_, bucket_count_, decode_type_); + switch (task_type_) { case kInRaster: RecordMicrosecondTimesUmaByDecodeType( diff --git a/chromium/cc/base/devtools_instrumentation.h b/chromium/cc/base/devtools_instrumentation.h index 12e4c9c7016..8c2fd998669 100644 --- a/chromium/cc/base/devtools_instrumentation.h +++ b/chromium/cc/base/devtools_instrumentation.h @@ -66,9 +66,9 @@ class CC_BASE_EXPORT ScopedLayerTask { class CC_BASE_EXPORT ScopedImageTask { public: - enum ImageType { kWebP, kJpeg, kOther }; + enum ImageType { kAvif, kBmp, kGif, kIco, kJpeg, kPng, kWebP, kOther }; - ScopedImageTask(ImageType image_type) + explicit ScopedImageTask(ImageType image_type) : image_type_(image_type), start_time_(base::TimeTicks::Now()) {} ScopedImageTask(const ScopedImageTask&) = delete; ~ScopedImageTask() = default; diff --git a/chromium/cc/base/features.cc b/chromium/cc/base/features.cc index 8c0c599f83d..5c230565fd2 100644 --- a/chromium/cc/base/features.cc +++ b/chromium/cc/base/features.cc @@ -12,6 +12,10 @@ namespace features { const base::Feature kImpulseScrollAnimations = { "ImpulseScrollAnimations", base::FEATURE_DISABLED_BY_DEFAULT}; +// Whether the compositor should attempt to sync with the scroll handlers before submitting a frame. +const base::Feature kSynchronizedScrolling = {"SynchronizedScrolling", + base::FEATURE_ENABLED_BY_DEFAULT}; + // When enabled BeginMainFrame does not wait for activation in the compositor // thread for texture layers (crbug.com/1046463) const base::Feature kTextureLayerSkipWaitForActivation{ diff --git a/chromium/cc/base/features.h b/chromium/cc/base/features.h index 56c620c6eea..ea86ee616d8 100644 --- a/chromium/cc/base/features.h +++ b/chromium/cc/base/features.h @@ -12,6 +12,7 @@ namespace features { CC_BASE_EXPORT extern const base::Feature kImpulseScrollAnimations; +CC_BASE_EXPORT extern const base::Feature kSynchronizedScrolling; CC_BASE_EXPORT extern const base::Feature kTextureLayerSkipWaitForActivation; #if !defined(OS_ANDROID) diff --git a/chromium/cc/base/list_container.h b/chromium/cc/base/list_container.h index 27ba06b9192..a01d07958dd 100644 --- a/chromium/cc/base/list_container.h +++ b/chromium/cc/base/list_container.h @@ -9,7 +9,7 @@ #include <memory> -#include "base/logging.h" +#include "base/check.h" #include "base/optional.h" #include "cc/base/list_container_helper.h" diff --git a/chromium/cc/base/list_container_helper.cc b/chromium/cc/base/list_container_helper.cc index afd386e3a88..7b594b4a458 100644 --- a/chromium/cc/base/list_container_helper.cc +++ b/chromium/cc/base/list_container_helper.cc @@ -7,6 +7,7 @@ #include <stddef.h> #include <algorithm> +#include <cstring> #include <vector> #include "base/check_op.h" diff --git a/chromium/cc/base/math_util.h b/chromium/cc/base/math_util.h index 1001124d9af..a5f30e02252 100644 --- a/chromium/cc/base/math_util.h +++ b/chromium/cc/base/math_util.h @@ -9,7 +9,7 @@ #include <memory> #include <vector> -#include "base/logging.h" +#include "base/check.h" #include "base/numerics/ranges.h" #include "build/build_config.h" #include "cc/base/base_export.h" diff --git a/chromium/cc/base/reverse_spiral_iterator.h b/chromium/cc/base/reverse_spiral_iterator.h index 3fef364871d..1d07381e62d 100644 --- a/chromium/cc/base/reverse_spiral_iterator.h +++ b/chromium/cc/base/reverse_spiral_iterator.h @@ -5,7 +5,6 @@ #ifndef CC_BASE_REVERSE_SPIRAL_ITERATOR_H_ #define CC_BASE_REVERSE_SPIRAL_ITERATOR_H_ -#include "base/logging.h" #include "cc/base/base_export.h" #include "cc/base/index_rect.h" diff --git a/chromium/cc/base/rtree.h b/chromium/cc/base/rtree.h index 2ee4e7cb588..9e92f955de4 100644 --- a/chromium/cc/base/rtree.h +++ b/chromium/cc/base/rtree.h @@ -13,7 +13,7 @@ #include <map> #include <vector> -#include "base/logging.h" +#include "base/check_op.h" #include "base/numerics/clamped_math.h" #include "ui/gfx/geometry/rect.h" diff --git a/chromium/cc/base/spiral_iterator.h b/chromium/cc/base/spiral_iterator.h index af64b76afde..3ed081688e7 100644 --- a/chromium/cc/base/spiral_iterator.h +++ b/chromium/cc/base/spiral_iterator.h @@ -5,7 +5,6 @@ #ifndef CC_BASE_SPIRAL_ITERATOR_H_ #define CC_BASE_SPIRAL_ITERATOR_H_ -#include "base/logging.h" #include "cc/base/base_export.h" #include "cc/base/index_rect.h" diff --git a/chromium/cc/base/switches.h b/chromium/cc/base/switches.h index 7b6c6b398e0..12615d5dcdc 100644 --- a/chromium/cc/base/switches.h +++ b/chromium/cc/base/switches.h @@ -7,7 +7,7 @@ #ifndef CC_BASE_SWITCHES_H_ #define CC_BASE_SWITCHES_H_ -#include "base/logging.h" +#include "base/check.h" #include "cc/base/base_export.h" // Since cc is used from the render process, anything that goes here also needs diff --git a/chromium/cc/base/tiling_data.h b/chromium/cc/base/tiling_data.h index a8b9ee7ec6c..ac294ac5ad6 100644 --- a/chromium/cc/base/tiling_data.h +++ b/chromium/cc/base/tiling_data.h @@ -7,7 +7,7 @@ #include <utility> -#include "base/logging.h" +#include "base/check_op.h" #include "cc/base/base_export.h" #include "cc/base/index_rect.h" #include "cc/base/reverse_spiral_iterator.h" diff --git a/chromium/cc/input/browser_controls_offset_manager.cc b/chromium/cc/input/browser_controls_offset_manager.cc index ea4c6e27993..5ebc925c1e6 100644 --- a/chromium/cc/input/browser_controls_offset_manager.cc +++ b/chromium/cc/input/browser_controls_offset_manager.cc @@ -207,17 +207,20 @@ void BrowserControlsOffsetManager::OnBrowserControlsParamsChanged( return; } + // We continue to update both top and bottom controls even if one has a height + // of 0 so that animations work properly. So here, we should preserve the + // ratios even if the controls height is 0. float old_top_height = old_browser_controls_params_.top_controls_height; float new_top_ratio = TopControlsHeight() ? TopControlsShownRatio() * old_top_height / TopControlsHeight() - : 0.f; + : TopControlsShownRatio(); float old_bottom_height = old_browser_controls_params_.bottom_controls_height; float new_bottom_ratio = BottomControlsHeight() ? BottomControlsShownRatio() * old_bottom_height / BottomControlsHeight() - : 0.f; + : BottomControlsShownRatio(); if (!animate_changes) { // If the min-heights changed when the controls were at the min-height, the @@ -268,8 +271,13 @@ void BrowserControlsOffsetManager::OnBrowserControlsParamsChanged( bool bottom_controls_need_animation = animate_changes; float top_target_ratio; - // If the top controls height changed when they were fully shown. - if (TopControlsShownRatio() == 1.f && TopControlsHeight() != old_top_height) { + // We can't animate if we don't have top controls. + if (!TopControlsHeight()) { + top_controls_need_animation = false; + + // If the top controls height changed when they were fully shown. + } else if (TopControlsShownRatio() == 1.f && + TopControlsHeight() != old_top_height) { top_target_ratio = 1.f; // i.e. new_height / new_height // If the top controls min-height changed when they were at the minimum @@ -284,9 +292,13 @@ void BrowserControlsOffsetManager::OnBrowserControlsParamsChanged( } float bottom_target_ratio; - // If the bottom controls height changed when they were fully shown. - if (BottomControlsShownRatio() == 1.f && - BottomControlsHeight() != old_bottom_height) { + // We can't animate if we don't have bottom controls. + if (!BottomControlsHeight()) { + bottom_controls_need_animation = false; + + // If the bottom controls height changed when they were fully shown. + } else if (BottomControlsShownRatio() == 1.f && + BottomControlsHeight() != old_bottom_height) { bottom_target_ratio = 1.f; // i.e. new_height / new_height // If the bottom controls min-height changed when they were at the minimum diff --git a/chromium/cc/input/browser_controls_offset_manager_unittest.cc b/chromium/cc/input/browser_controls_offset_manager_unittest.cc index 8c443fd0bee..507ca1f3a5a 100644 --- a/chromium/cc/input/browser_controls_offset_manager_unittest.cc +++ b/chromium/cc/input/browser_controls_offset_manager_unittest.cc @@ -1087,5 +1087,61 @@ TEST(BrowserControlsOffsetManagerTest, ScrollWithMinHeightSetForBothControls) { manager->ScrollEnd(); } +TEST(BrowserControlsOffsetManagerTest, ChangingBottomHeightFromZeroAnimates) { + MockBrowserControlsOffsetManagerClient client(100, 0.5f, 0.5f); + client.SetBrowserControlsParams({100, 30, 0, 0, false, false}); + BrowserControlsOffsetManager* manager = client.manager(); + EXPECT_FLOAT_EQ(1.f, client.CurrentTopControlsShownRatio()); + EXPECT_FLOAT_EQ(1.f, client.CurrentBottomControlsShownRatio()); + + // Set the bottom controls height to 100 with animation. + client.SetBrowserControlsParams({100, 30, 100, 0, true, false}); + EXPECT_TRUE(manager->HasAnimation()); + // The bottom controls should be hidden in the beginning. + EXPECT_FLOAT_EQ(0.f, manager->ContentBottomOffset()); + EXPECT_FLOAT_EQ(0.f, client.CurrentBottomControlsShownRatio()); + + base::TimeTicks time = base::TimeTicks::Now(); + + // First animate will establish the animaion. + float previous_ratio = manager->BottomControlsShownRatio(); + manager->Animate(time); + EXPECT_EQ(manager->BottomControlsShownRatio(), previous_ratio); + + while (manager->HasAnimation()) { + previous_ratio = manager->BottomControlsShownRatio(); + time = base::TimeDelta::FromMicroseconds(100) + time; + manager->Animate(time); + EXPECT_GT(manager->BottomControlsShownRatio(), previous_ratio); + } + + // Now the bottom controls should be fully shown. + EXPECT_FLOAT_EQ(100.f, manager->ContentBottomOffset()); + EXPECT_FLOAT_EQ(1.f, client.CurrentBottomControlsShownRatio()); +} + +TEST(BrowserControlsOffsetManagerTest, + ChangingControlsHeightToZeroWithAnimationIsNoop) { + MockBrowserControlsOffsetManagerClient client(100, 0.5f, 0.5f); + client.SetBrowserControlsParams({100, 20, 80, 10, false, false}); + BrowserControlsOffsetManager* manager = client.manager(); + EXPECT_FLOAT_EQ(1.f, client.CurrentTopControlsShownRatio()); + EXPECT_FLOAT_EQ(1.f, client.CurrentBottomControlsShownRatio()); + + // Set the bottom controls height to 0 with animation. + client.SetBrowserControlsParams({100, 20, 0, 0, true, false}); + + // There shouldn't be an animation because we can't animate controls with 0 + // height. + EXPECT_FALSE(manager->HasAnimation()); + // Also, the bottom controls ratio should stay the same. + EXPECT_FLOAT_EQ(1.f, client.CurrentBottomControlsShownRatio()); + + // Increase the top controls height with animation. + client.SetBrowserControlsParams({120, 20, 0, 0, true, false}); + // This shouldn't override the bottom controls shown ratio. + EXPECT_FLOAT_EQ(1.f, client.CurrentBottomControlsShownRatio()); +} + } // namespace } // namespace cc diff --git a/chromium/cc/input/input_handler.cc b/chromium/cc/input/input_handler.cc deleted file mode 100644 index 55ca46ef2c0..00000000000 --- a/chromium/cc/input/input_handler.cc +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2014 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/input/input_handler.h" - -namespace cc { - -InputHandlerScrollResult::InputHandlerScrollResult() - : did_scroll(false), did_overscroll_root(false) { -} - -InputHandlerPointerResult::InputHandlerPointerResult() - : type(kUnhandled), - scroll_units(ui::ScrollGranularity::kScrollByPrecisePixel) {} - -} // namespace cc diff --git a/chromium/cc/input/input_handler.h b/chromium/cc/input/input_handler.h index eaaa9afde45..43309848b23 100644 --- a/chromium/cc/input/input_handler.h +++ b/chromium/cc/input/input_handler.h @@ -50,13 +50,14 @@ enum class ScrollBeginThreadState { }; struct CC_EXPORT InputHandlerPointerResult { - InputHandlerPointerResult(); + InputHandlerPointerResult() = default; // Tells what type of processing occurred in the input handler as a result of // the pointer event. - PointerResultType type; + PointerResultType type = kUnhandled; // Tells what scroll_units should be used. - ui::ScrollGranularity scroll_units; + ui::ScrollGranularity scroll_units = + ui::ScrollGranularity::kScrollByPrecisePixel; // If the input handler processed the event as a scrollbar scroll, it will // return a gfx::ScrollOffset that produces the necessary scroll. However, @@ -73,11 +74,11 @@ struct CC_EXPORT InputHandlerPointerResult { }; struct CC_EXPORT InputHandlerScrollResult { - InputHandlerScrollResult(); + InputHandlerScrollResult() = default; // Did any layer scroll as a result this ScrollUpdate call? - bool did_scroll; + bool did_scroll = false; // Was any of the scroll delta argument to this ScrollUpdate call not used? - bool did_overscroll_root; + bool did_overscroll_root = false; // The total overscroll that has been accumulated by all ScrollUpdate calls // that have had overscroll since the last ScrollBegin call. This resets upon // a ScrollUpdate with no overscroll. @@ -139,17 +140,25 @@ class CC_EXPORT InputHandler { InputHandler& operator=(const InputHandler&) = delete; struct ScrollStatus { - ScrollStatus() - : thread(SCROLL_ON_IMPL_THREAD), - main_thread_scrolling_reasons( - MainThreadScrollingReason::kNotScrollingOnMain), - bubble(false) {} + ScrollStatus() = default; ScrollStatus(ScrollThread thread, uint32_t main_thread_scrolling_reasons) : thread(thread), main_thread_scrolling_reasons(main_thread_scrolling_reasons) {} - ScrollThread thread; - uint32_t main_thread_scrolling_reasons; - bool bubble; + ScrollStatus(ScrollThread thread, + uint32_t main_thread_scrolling_reasons, + bool needs_main_thread_hit_test) + : thread(thread), + main_thread_scrolling_reasons(main_thread_scrolling_reasons), + needs_main_thread_hit_test(needs_main_thread_hit_test) {} + ScrollThread thread = SCROLL_ON_IMPL_THREAD; + uint32_t main_thread_scrolling_reasons = + MainThreadScrollingReason::kNotScrollingOnMain; + bool bubble = false; + + // Used only in scroll unification. Tells the caller that the input handler + // detected a case where it cannot reliably target a scroll node and needs + // the main thread to perform a hit test. + bool needs_main_thread_hit_test = false; }; enum class TouchStartOrMoveEventListenerType { @@ -256,7 +265,7 @@ class CC_EXPORT InputHandler { // suppress scrolling by consuming touch events that started at // |viewport_point|, and whether |viewport_point| is on the currently // scrolling layer. - // |out_touch_action| is assigned the whitelisted touch action for the + // |out_touch_action| is assigned the allowed touch action for the // |viewport_point|. In the case there are no touch handlers or touch action // regions, |out_touch_action| is assigned TouchAction::kAuto since the // default touch action is auto. diff --git a/chromium/cc/input/main_thread_scrolling_reason.cc b/chromium/cc/input/main_thread_scrolling_reason.cc index b84c31dd04c..a116970e49b 100644 --- a/chromium/cc/input/main_thread_scrolling_reason.cc +++ b/chromium/cc/input/main_thread_scrolling_reason.cc @@ -44,14 +44,10 @@ void MainThreadScrollingReason::AddToTracedValue( traced_value.AppendString("Handling scroll from main thread"); if (reasons & kHasTransformAndLCDText) traced_value.AppendString("Has transform and LCD text"); - if (reasons & kBackgroundNotOpaqueInRectAndLCDText) - traced_value.AppendString("Background is not opaque in rect and LCD text"); - if (reasons & kCantPaintScrollingBackground) - traced_value.AppendString("Can't paint scrolling background"); - if (reasons & kHasClipRelatedProperty) - traced_value.AppendString("Has clip related property"); - if (reasons & kIsNotStackingContextAndLCDText) - traced_value.AppendString("Is not stacking context and LCD text"); + if (reasons & kNotOpaqueForTextAndLCDText) + traced_value.AppendString("Not opaque for text and LCD text"); + if (reasons & kCantPaintScrollingBackgroundAndLCDText) + traced_value.AppendString("Can't paint scrolling background and LCD text"); // Transient scrolling reasons. if (reasons & kNonFastScrollableRegion) diff --git a/chromium/cc/input/main_thread_scrolling_reason.h b/chromium/cc/input/main_thread_scrolling_reason.h index cd129cf69fc..8354bbb401d 100644 --- a/chromium/cc/input/main_thread_scrolling_reason.h +++ b/chromium/cc/input/main_thread_scrolling_reason.h @@ -44,10 +44,8 @@ struct CC_EXPORT MainThreadScrollingReason { // screen position; transparency and transforms break this. kNonCompositedReasonsFirst = 17, kHasTransformAndLCDText = 1 << 17, - kBackgroundNotOpaqueInRectAndLCDText = 1 << 18, - kCantPaintScrollingBackground = 1 << 19, - kHasClipRelatedProperty = 1 << 20, - kIsNotStackingContextAndLCDText = 1 << 22, + kNotOpaqueForTextAndLCDText = 1 << 18, + kCantPaintScrollingBackgroundAndLCDText = 1 << 19, kNonCompositedReasonsLast = 22, // Transient scrolling reasons. These are computed for each scroll begin. @@ -69,9 +67,8 @@ struct CC_EXPORT MainThreadScrollingReason { }; static const uint32_t kNonCompositedReasons = - kHasTransformAndLCDText | kBackgroundNotOpaqueInRectAndLCDText | - kCantPaintScrollingBackground | kHasClipRelatedProperty | - kIsNotStackingContextAndLCDText; + kHasTransformAndLCDText | kNotOpaqueForTextAndLCDText | + kCantPaintScrollingBackgroundAndLCDText; // Returns true if the given MainThreadScrollingReason can be set by the main // thread. diff --git a/chromium/cc/input/main_thread_scrolling_reason_unittest.cc b/chromium/cc/input/main_thread_scrolling_reason_unittest.cc index 7cf3f40d164..4f8b75516af 100644 --- a/chromium/cc/input/main_thread_scrolling_reason_unittest.cc +++ b/chromium/cc/input/main_thread_scrolling_reason_unittest.cc @@ -20,10 +20,8 @@ TEST_F(MainThreadScrollingReasonTest, AsText) { "Frame overlay, " "Handling scroll from main thread, " "Has transform and LCD text, " - "Background is not opaque in rect and LCD text, " - "Can't paint scrolling background, " - "Has clip related property, " - "Is not stacking context and LCD text, " + "Not opaque for text and LCD text, " + "Can't paint scrolling background and LCD text, " "Non fast scrollable region, " "Failed hit test, " "No scrolling layer, " diff --git a/chromium/cc/input/scroll_elasticity_helper.cc b/chromium/cc/input/scroll_elasticity_helper.cc index 739daaa72a3..451bfcd81ba 100644 --- a/chromium/cc/input/scroll_elasticity_helper.cc +++ b/chromium/cc/input/scroll_elasticity_helper.cc @@ -18,6 +18,7 @@ class ScrollElasticityHelperImpl : public ScrollElasticityHelper { bool IsUserScrollable() const override; gfx::Vector2dF StretchAmount() const override; + gfx::Size ScrollBounds() const override; void SetStretchAmount(const gfx::Vector2dF& stretch_amount) override; gfx::ScrollOffset ScrollOffset() const override; gfx::ScrollOffset MaxScrollOffset() const override; @@ -46,6 +47,12 @@ gfx::Vector2dF ScrollElasticityHelperImpl::StretchAmount() const { return host_impl_->active_tree()->elastic_overscroll()->Current(true); } +gfx::Size ScrollElasticityHelperImpl::ScrollBounds() const { + return host_impl_->OuterViewportScrollNode() + ? host_impl_->OuterViewportScrollNode()->container_bounds + : gfx::Size(); +} + void ScrollElasticityHelperImpl::SetStretchAmount( const gfx::Vector2dF& stretch_amount) { if (stretch_amount == StretchAmount()) diff --git a/chromium/cc/input/scroll_elasticity_helper.h b/chromium/cc/input/scroll_elasticity_helper.h index 81281310ecb..4ae282fe51b 100644 --- a/chromium/cc/input/scroll_elasticity_helper.h +++ b/chromium/cc/input/scroll_elasticity_helper.h @@ -8,6 +8,7 @@ #include "base/time/time.h" #include "cc/cc_export.h" #include "ui/gfx/geometry/scroll_offset.h" +#include "ui/gfx/geometry/size.h" #include "ui/gfx/geometry/vector2d_f.h" namespace cc { @@ -54,6 +55,9 @@ class CC_EXPORT ScrollElasticityHelper { virtual bool IsUserScrollable() const = 0; + // The bounds of the root scroller. + virtual gfx::Size ScrollBounds() const = 0; + // The amount that the view is stretched past the normal allowable bounds. virtual gfx::Vector2dF StretchAmount() const = 0; virtual void SetStretchAmount(const gfx::Vector2dF& stretch_amount) = 0; diff --git a/chromium/cc/input/scroll_state.h b/chromium/cc/input/scroll_state.h index a73b9b117c4..8ac3850ca51 100644 --- a/chromium/cc/input/scroll_state.h +++ b/chromium/cc/input/scroll_state.h @@ -86,6 +86,14 @@ class CC_EXPORT ScrollState { // it's a scroll update gfx::ScrollOffset DeltaOrHint() const; + ElementId target_element_id() const { + return data_.current_native_scrolling_element(); + } + + bool is_main_thread_hit_tested() const { + return data_.is_main_thread_hit_tested; + } + ScrollStateData* data() { return &data_; } private: diff --git a/chromium/cc/input/scroll_state_data.cc b/chromium/cc/input/scroll_state_data.cc index 4c472790f15..f18676f4b85 100644 --- a/chromium/cc/input/scroll_state_data.cc +++ b/chromium/cc/input/scroll_state_data.cc @@ -25,7 +25,8 @@ ScrollStateData::ScrollStateData() delta_granularity(ui::ScrollGranularity::kScrollByPrecisePixel), caused_scroll_x(false), caused_scroll_y(false), - is_scroll_chain_cut(false) {} + is_scroll_chain_cut(false), + is_main_thread_hit_tested(false) {} ScrollStateData::ScrollStateData(const ScrollStateData& other) = default; diff --git a/chromium/cc/input/scroll_state_data.h b/chromium/cc/input/scroll_state_data.h index 297517031ae..a3a2755c61b 100644 --- a/chromium/cc/input/scroll_state_data.h +++ b/chromium/cc/input/scroll_state_data.h @@ -70,12 +70,18 @@ class CC_EXPORT ScrollStateData { ElementId current_native_scrolling_element() const; void set_current_native_scrolling_element(ElementId element_id); + // Used in scroll unification to specify that a scroll state has been hit + // tested on the main thread. If this is true, the hit test result will be + // placed in the current_native_scrolling_element_. + bool is_main_thread_hit_tested; + private: // The id of the last native element to respond to a scroll, or 0 if none // exists. // TODO(bokan): In the compositor, this is now only used as an override to - // scroller targeting, i.e. we'll latch scrolling to the specified - // element_id. It will be renamed when the main thread is also converted. + // scroller targeting. I.e. we'll latch scrolling to the specified + // element_id. It will be renamed to a better name (target_element_id?) when + // the main thread is also converted. ElementId current_native_scrolling_element_; }; diff --git a/chromium/cc/input/scroll_utils.cc b/chromium/cc/input/scroll_utils.cc new file mode 100644 index 00000000000..e9c036e3896 --- /dev/null +++ b/chromium/cc/input/scroll_utils.cc @@ -0,0 +1,45 @@ +// 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 "cc/input/scroll_utils.h" + +#include "base/numerics/ranges.h" +#include "ui/gfx/geometry/size_f.h" +#include "ui/gfx/geometry/vector2d_f.h" + +namespace cc { + +// static +gfx::Vector2dF ScrollUtils::ResolveScrollPercentageToPixels( + const gfx::Vector2dF& delta, + const gfx::SizeF& scroller, + const gfx::SizeF& viewport) { + // Work with unsigned values and keep sign information in sign_x / sign_y. + float sign_x = std::signbit(delta.x()) ? -1 : 1; + float sign_y = std::signbit(delta.y()) ? -1 : 1; + float delta_x = std::abs(delta.x()); + float delta_y = std::abs(delta.y()); + + // Resolved deltas in percent based scrolling are clamped at min by 16 pixels. + float min = kMinPixelDeltaForPercentBasedScroll; + + // Resolve and clamp horizontal scroll + if (delta_x > 0) { + delta_x = delta_x * std::min(scroller.width(), viewport.width()); + if (delta_x < min) + delta_x = min; + } + + // Resolve and clamps vertical scroll. + if (delta_y > 0) { + delta_y = delta_y * std::min(scroller.height(), viewport.height()); + if (delta_y < min) + delta_y = min; + } + + return gfx::Vector2dF(std::copysign(delta_x, sign_x), + std::copysign(delta_y, sign_y)); +} + +} // namespace cc diff --git a/chromium/cc/input/scroll_utils.h b/chromium/cc/input/scroll_utils.h new file mode 100644 index 00000000000..34fb3be7773 --- /dev/null +++ b/chromium/cc/input/scroll_utils.h @@ -0,0 +1,43 @@ +// 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 CC_INPUT_SCROLL_UTILS_H_ +#define CC_INPUT_SCROLL_UTILS_H_ + +#include "cc/cc_export.h" + +namespace gfx { +class Vector2dF; +class SizeF; +} // namespace gfx + +namespace cc { + +static constexpr int kPixelsPerLineStep = 40; +static constexpr float kMinFractionToStepWhenPaging = 0.875f; + +// Each directional scroll for percentage-based units should scroll 1/8th of +// the scrollable area. +static constexpr float kPercentDeltaForDirectionalScroll = 0.125f; + +// Scroll deltas are lower-bounded by 16 physical pixels in percent-based +// scrolls. +static constexpr float kMinPixelDeltaForPercentBasedScroll = 16; + +// Class for scroll helper methods in cc and blink. +class CC_EXPORT ScrollUtils { + public: + // Transforms a |scroll_delta| in percent units to pixel units based in its + // |scroller_size|. Clamps it by 16 pixels to avoid too small deltas for tiny + // scrollers and 12.5% of |viewport_size| to avoid too large deltas. + // Inputs and output muest be in physical pixels. + static gfx::Vector2dF ResolveScrollPercentageToPixels( + const gfx::Vector2dF& scroll_delta, + const gfx::SizeF& scroller_size, + const gfx::SizeF& viewport_size); +}; + +} // namespace cc + +#endif // CC_INPUT_SCROLL_UTILS_H_ diff --git a/chromium/cc/input/scrollbar.h b/chromium/cc/input/scrollbar.h index af8cef429b1..4fc7a3e57e9 100644 --- a/chromium/cc/input/scrollbar.h +++ b/chromium/cc/input/scrollbar.h @@ -11,13 +11,6 @@ #include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/rect.h" -static constexpr int kPixelsPerLineStep = 40; -static constexpr float kMinFractionToStepWhenPaging = 0.875f; - -// Each directional scroll for percentage-based units should scroll 1/8th of -// the scrollable area. -static constexpr float kPercentDeltaForDirectionalScroll = 0.125f; - // Autoscrolling (on the main thread) happens by applying a delta every 50ms. // Hence, pixels per second for a autoscroll cc animation can be calculated as: // autoscroll velocity = delta / 0.05 sec = delta x 20 @@ -58,6 +51,7 @@ class Scrollbar : public base::RefCounted<Scrollbar> { virtual bool IsOverlay() const = 0; virtual bool HasThumb() const = 0; virtual bool SupportsDragSnapBack() const = 0; + virtual bool JumpOnTrackClick() const = 0; // The following rects are all relative to the scrollbar's origin. // The location of ThumbRect reflects scroll offset, but cc will ignore it diff --git a/chromium/cc/input/scrollbar_animation_controller.cc b/chromium/cc/input/scrollbar_animation_controller.cc index 2435aa156fe..50242fb337c 100644 --- a/chromium/cc/input/scrollbar_animation_controller.cc +++ b/chromium/cc/input/scrollbar_animation_controller.cc @@ -51,8 +51,6 @@ ScrollbarAnimationController::ScrollbarAnimationController( is_animating_(false), animation_change_(NONE), scroll_element_id_(scroll_element_id), - currently_scrolling_(false), - show_in_fast_scroll_(false), opacity_(initial_opacity), show_scrollbars_on_scroll_gesture_(false), need_thinning_animation_(false), @@ -73,8 +71,6 @@ ScrollbarAnimationController::ScrollbarAnimationController( is_animating_(false), animation_change_(NONE), scroll_element_id_(scroll_element_id), - currently_scrolling_(false), - show_in_fast_scroll_(false), opacity_(initial_opacity), show_scrollbars_on_scroll_gesture_(true), need_thinning_animation_(true), @@ -182,25 +178,6 @@ void ScrollbarAnimationController::RunAnimationFrame(float progress) { StopAnimation(); } -void ScrollbarAnimationController::DidScrollBegin() { - currently_scrolling_ = true; -} - -void ScrollbarAnimationController::DidScrollEnd() { - bool has_scrolled = show_in_fast_scroll_; - show_in_fast_scroll_ = false; - - currently_scrolling_ = false; - - // We don't fade out scrollbar if they need thinning animation and mouse is - // near. - if (need_thinning_animation_ && MouseIsNearAnyScrollbar()) - return; - - if (has_scrolled && !tickmarks_showing_) - PostDelayedAnimation(FADE_OUT); -} - void ScrollbarAnimationController::DidScrollUpdate() { UpdateScrollbarState(); } @@ -213,19 +190,13 @@ void ScrollbarAnimationController::UpdateScrollbarState() { Show(); - // As an optimization, we avoid spamming fade delay tasks during active fast - // scrolls. But if we're not within one, we need to post every scroll update. - if (!currently_scrolling_) { - // We don't fade out scrollbar if they need thinning animation (Aura - // Overlay) and mouse is near or tickmarks show. - if (need_thinning_animation_) { - if (!MouseIsNearAnyScrollbar() && !tickmarks_showing_) - PostDelayedAnimation(FADE_OUT); - } else { + // We don't fade out scrollbar if they need thinning animation (Aura + // Overlay) and mouse is near or tickmarks show. + if (need_thinning_animation_) { + if (!MouseIsNearAnyScrollbar() && !tickmarks_showing_) PostDelayedAnimation(FADE_OUT); - } } else { - show_in_fast_scroll_ = true; + PostDelayedAnimation(FADE_OUT); } if (need_thinning_animation_) { diff --git a/chromium/cc/input/scrollbar_animation_controller.h b/chromium/cc/input/scrollbar_animation_controller.h index 67a9ed5c8fc..1f2c22af131 100644 --- a/chromium/cc/input/scrollbar_animation_controller.h +++ b/chromium/cc/input/scrollbar_animation_controller.h @@ -72,9 +72,6 @@ class CC_EXPORT ScrollbarAnimationController { // Effect both Android and Aura Overlay Scrollbar. void DidScrollUpdate(); - void DidScrollBegin(); - void DidScrollEnd(); - void DidMouseDown(); void DidMouseUp(); void DidMouseLeave(); @@ -150,8 +147,6 @@ class CC_EXPORT ScrollbarAnimationController { AnimationChange animation_change_; const ElementId scroll_element_id_; - bool currently_scrolling_; - bool show_in_fast_scroll_; base::CancelableOnceClosure delayed_scrollbar_animation_; diff --git a/chromium/cc/input/scrollbar_animation_controller_unittest.cc b/chromium/cc/input/scrollbar_animation_controller_unittest.cc index f9593321d61..985f45bae4b 100644 --- a/chromium/cc/input/scrollbar_animation_controller_unittest.cc +++ b/chromium/cc/input/scrollbar_animation_controller_unittest.cc @@ -163,9 +163,7 @@ TEST_F(ScrollbarAnimationControllerAuraOverlayTest, AppearOnResize) { base::TimeTicks time; time += base::TimeDelta::FromSeconds(1); - scrollbar_controller_->DidScrollBegin(); scrollbar_controller_->DidScrollUpdate(); - scrollbar_controller_->DidScrollEnd(); ExpectScrollbarsOpacity(1); // Make the Layer non-scrollable, scrollbar disappears. @@ -199,13 +197,9 @@ TEST_F(ScrollbarAnimationControllerAuraOverlayTest, HideOnResize) { scroll_layer_->UpdateScrollable(); UpdateActiveTreeDrawProperties(); - scrollbar_controller_->DidScrollBegin(); - scrollbar_controller_->DidScrollUpdate(); EXPECT_FLOAT_EQ(1, h_scrollbar_layer_->Opacity()); - scrollbar_controller_->DidScrollEnd(); - // Shrink along Y axis and expand along X, horizontal scrollbar // should disappear. clip_layer_->SetBounds(gfx::Size(200, 100)); @@ -214,12 +208,8 @@ TEST_F(ScrollbarAnimationControllerAuraOverlayTest, HideOnResize) { scroll_layer_->UpdateScrollable(); UpdateActiveTreeDrawProperties(); - scrollbar_controller_->DidScrollBegin(); - scrollbar_controller_->DidScrollUpdate(); EXPECT_FLOAT_EQ(0.0f, h_scrollbar_layer_->Opacity()); - - scrollbar_controller_->DidScrollEnd(); } // Scroll content. Confirm the scrollbar appears and fades out. @@ -232,7 +222,6 @@ TEST_F(ScrollbarAnimationControllerAuraOverlayTest, BasicAppearAndFadeOut) { EXPECT_TRUE(scrollbar_controller_->ScrollbarsHidden()); // Scrollbar should appear only on scroll update. - scrollbar_controller_->DidScrollBegin(); ExpectScrollbarsOpacity(0); EXPECT_TRUE(scrollbar_controller_->ScrollbarsHidden()); @@ -240,7 +229,6 @@ TEST_F(ScrollbarAnimationControllerAuraOverlayTest, BasicAppearAndFadeOut) { ExpectScrollbarsOpacity(1); EXPECT_FALSE(scrollbar_controller_->ScrollbarsHidden()); - scrollbar_controller_->DidScrollEnd(); ExpectScrollbarsOpacity(1); EXPECT_FALSE(scrollbar_controller_->ScrollbarsHidden()); @@ -295,9 +283,7 @@ TEST_F(ScrollbarAnimationControllerAuraOverlayTest, base::TimeTicks time; time += base::TimeDelta::FromSeconds(1); - scrollbar_controller_->DidScrollBegin(); scrollbar_controller_->DidScrollUpdate(); - scrollbar_controller_->DidScrollEnd(); // An fade out animation should have been enqueued. EXPECT_EQ(kFadeDelay, client_.delay()); @@ -355,9 +341,7 @@ TEST_F(ScrollbarAnimationControllerAuraOverlayTest, MoveNearAndDontFadeOut) { base::TimeTicks time; time += base::TimeDelta::FromSeconds(1); - scrollbar_controller_->DidScrollBegin(); scrollbar_controller_->DidScrollUpdate(); - scrollbar_controller_->DidScrollEnd(); // An fade out animation should have been enqueued. EXPECT_EQ(kFadeDelay, client_.delay()); @@ -396,9 +380,7 @@ TEST_F(ScrollbarAnimationControllerAuraOverlayTest, MoveOverAndDontFadeOut) { base::TimeTicks time; time += base::TimeDelta::FromSeconds(1); - scrollbar_controller_->DidScrollBegin(); scrollbar_controller_->DidScrollUpdate(); - scrollbar_controller_->DidScrollEnd(); // An fade out animation should have been enqueued. EXPECT_EQ(kFadeDelay, client_.delay()); @@ -438,9 +420,7 @@ TEST_F(ScrollbarAnimationControllerAuraOverlayTest, base::TimeTicks time; time += base::TimeDelta::FromSeconds(1); - scrollbar_controller_->DidScrollBegin(); scrollbar_controller_->DidScrollUpdate(); - scrollbar_controller_->DidScrollEnd(); // An fade out animation should have been enqueued. EXPECT_EQ(kFadeDelay, client_.delay()); @@ -466,9 +446,7 @@ TEST_F(ScrollbarAnimationControllerAuraOverlayTest, base::TimeTicks time; time += base::TimeDelta::FromSeconds(1); - scrollbar_controller_->DidScrollBegin(); scrollbar_controller_->DidScrollUpdate(); - scrollbar_controller_->DidScrollEnd(); // An fade out animation should have been enqueued. EXPECT_EQ(kFadeDelay, client_.delay()); @@ -502,9 +480,7 @@ TEST_F(ScrollbarAnimationControllerAuraOverlayTest, DontFadeWhileCaptured) { base::TimeTicks time; time += base::TimeDelta::FromSeconds(1); - scrollbar_controller_->DidScrollBegin(); scrollbar_controller_->DidScrollUpdate(); - scrollbar_controller_->DidScrollEnd(); // An fade out animation should have been enqueued. EXPECT_EQ(kFadeDelay, client_.delay()); @@ -540,9 +516,7 @@ TEST_F(ScrollbarAnimationControllerAuraOverlayTest, FadeAfterReleasedFar) { base::TimeTicks time; time += base::TimeDelta::FromSeconds(1); - scrollbar_controller_->DidScrollBegin(); scrollbar_controller_->DidScrollUpdate(); - scrollbar_controller_->DidScrollEnd(); // An fade out animation should have been enqueued. EXPECT_EQ(kFadeDelay, client_.delay()); @@ -591,9 +565,7 @@ TEST_F(ScrollbarAnimationControllerAuraOverlayTest, DontFadeAfterReleasedNear) { base::TimeTicks time; time += base::TimeDelta::FromSeconds(1); - scrollbar_controller_->DidScrollBegin(); scrollbar_controller_->DidScrollUpdate(); - scrollbar_controller_->DidScrollEnd(); // An fade out animation should have been enqueued. EXPECT_EQ(kFadeDelay, client_.delay()); @@ -630,9 +602,7 @@ TEST_F(ScrollbarAnimationControllerAuraOverlayTest, base::TimeTicks time; time += base::TimeDelta::FromSeconds(1); - scrollbar_controller_->DidScrollBegin(); scrollbar_controller_->DidScrollUpdate(); - scrollbar_controller_->DidScrollEnd(); // A fade out animation should have been enqueued. Start it. EXPECT_EQ(kFadeDelay, client_.delay()); @@ -670,9 +640,7 @@ TEST_F(ScrollbarAnimationControllerAuraOverlayTest, TestCantCaptureWhenFaded) { base::TimeTicks time; time += base::TimeDelta::FromSeconds(1); - scrollbar_controller_->DidScrollBegin(); scrollbar_controller_->DidScrollUpdate(); - scrollbar_controller_->DidScrollEnd(); EXPECT_EQ(kFadeDelay, client_.delay()); EXPECT_FALSE(client_.start_fade().is_null()); @@ -748,7 +716,6 @@ TEST_F(ScrollbarAnimationControllerAuraOverlayTest, ScrollWithMouseNear) { EXPECT_FLOAT_EQ(kIdleThicknessScale, h_scrollbar_layer_->thumb_thickness_scale_factor()); - scrollbar_controller_->DidScrollBegin(); scrollbar_controller_->DidScrollUpdate(); // Now that we've received a scroll, we should be thick without an animation. @@ -756,7 +723,6 @@ TEST_F(ScrollbarAnimationControllerAuraOverlayTest, ScrollWithMouseNear) { // An animation for the fade should be either null or cancelled, since // mouse is still near the scrollbar. - scrollbar_controller_->DidScrollEnd(); EXPECT_TRUE(client_.start_fade().is_null() || client_.start_fade().IsCancelled()); @@ -775,29 +741,16 @@ TEST_F(ScrollbarAnimationControllerAuraOverlayTest, ScrollWithMouseNear) { h_scrollbar_layer_->thumb_thickness_scale_factor()); } -// Tests that main thread scroll updates immediatley queue a fade out animation -// without requiring a ScrollEnd. +// Tests that main thread scroll updates immediately queue a fade out animation TEST_F(ScrollbarAnimationControllerAuraOverlayTest, MainThreadScrollQueuesFade) { ASSERT_TRUE(client_.start_fade().is_null()); - // A ScrollUpdate without a ScrollBegin indicates a main thread scroll update - // so we should schedule a fade out animation without waiting for a ScrollEnd - // (which will never come). + // A ScrollUpdate indicates a main thread scroll update so we should schedule + // a fade out animation since there is no scroll end notification. scrollbar_controller_->DidScrollUpdate(); EXPECT_FALSE(client_.start_fade().is_null()); EXPECT_EQ(kFadeDelay, client_.delay()); - - client_.start_fade().Reset(); - - // If we got a ScrollBegin, we shouldn't schedule the fade out animation until - // we get a corresponding ScrollEnd. - scrollbar_controller_->DidScrollBegin(); - scrollbar_controller_->DidScrollUpdate(); - EXPECT_TRUE(client_.start_fade().is_null()); - scrollbar_controller_->DidScrollEnd(); - EXPECT_FALSE(client_.start_fade().is_null()); - EXPECT_EQ(kFadeDelay, client_.delay()); } // Tests that the fade effect is animated. @@ -806,9 +759,7 @@ TEST_F(ScrollbarAnimationControllerAuraOverlayTest, FadeAnimated) { time += base::TimeDelta::FromSeconds(1); // Scroll to make the scrollbars visible. - scrollbar_controller_->DidScrollBegin(); scrollbar_controller_->DidScrollUpdate(); - scrollbar_controller_->DidScrollEnd(); // Appearance is instant. ExpectScrollbarsOpacity(1); @@ -838,13 +789,10 @@ TEST_F(ScrollbarAnimationControllerAuraOverlayTest, NotifyChangedVisibility) { EXPECT_CALL(client_, DidChangeScrollbarVisibility()).Times(1); // Scroll to make the scrollbars visible. - scrollbar_controller_->DidScrollBegin(); scrollbar_controller_->DidScrollUpdate(); EXPECT_FALSE(scrollbar_controller_->ScrollbarsHidden()); Mock::VerifyAndClearExpectations(&client_); - scrollbar_controller_->DidScrollEnd(); - // Play out the fade out animation. We shouldn't notify that the scrollbars // are hidden until the animation is completly over. We can (but don't have // to) notify during the animation that the scrollbars are still visible. @@ -886,9 +834,7 @@ TEST_F(ScrollbarAnimationControllerAuraOverlayTest, MouseNearEach) { time += base::TimeDelta::FromSeconds(1); // Scroll to make the scrollbars visible. - scrollbar_controller_->DidScrollBegin(); scrollbar_controller_->DidScrollUpdate(); - scrollbar_controller_->DidScrollEnd(); // Near vertical scrollbar. scrollbar_controller_->DidMouseMove(NearVerticalScrollbarBegin(-1, 0)); @@ -979,9 +925,7 @@ TEST_F(ScrollbarAnimationControllerAuraOverlayTest, MouseNearBoth) { time += base::TimeDelta::FromSeconds(1); // Scroll to make the scrollbars visible. - scrollbar_controller_->DidScrollBegin(); scrollbar_controller_->DidScrollUpdate(); - scrollbar_controller_->DidScrollEnd(); // Move scrollbar thumb to the end of track. v_scrollbar_layer_->SetCurrentPos(100); @@ -1012,9 +956,7 @@ TEST_F(ScrollbarAnimationControllerAuraOverlayTest, time += base::TimeDelta::FromSeconds(1); // Scroll to make the scrollbars visible. - scrollbar_controller_->DidScrollBegin(); scrollbar_controller_->DidScrollUpdate(); - scrollbar_controller_->DidScrollEnd(); // Near vertical scrollbar. scrollbar_controller_->DidMouseMove(NearVerticalScrollbarBegin(-1, 0)); @@ -1076,9 +1018,7 @@ TEST_F(ScrollbarAnimationControllerAuraOverlayTest, MouseLeaveFadeOut) { scrollbar_controller_->DidMouseMove(NearVerticalScrollbarBegin(-1, 0)); // Scroll to make the scrollbars visible. - scrollbar_controller_->DidScrollBegin(); scrollbar_controller_->DidScrollUpdate(); - scrollbar_controller_->DidScrollEnd(); // Should not have delay fadeout animation. EXPECT_TRUE(client_.start_fade().is_null() || @@ -1323,11 +1263,9 @@ TEST_F(ScrollbarAnimationControllerAuraOverlayTest, TickmakrsShowHide) { client_.start_fade().IsCancelled()); // Scroll update with phase, no delay fade animation. - scrollbar_controller_->DidScrollBegin(); scrollbar_controller_->DidScrollUpdate(); EXPECT_TRUE(client_.start_fade().is_null() || client_.start_fade().IsCancelled()); - scrollbar_controller_->DidScrollEnd(); EXPECT_TRUE(client_.start_fade().is_null() || client_.start_fade().IsCancelled()); @@ -1430,13 +1368,11 @@ TEST_F(ScrollbarAnimationControllerAndroidTest, HiddenInBegin) { TEST_F(ScrollbarAnimationControllerAndroidTest, HiddenAfterNonScrollingGesture) { scrollbar_layer_->SetOverlayScrollbarLayerOpacityAnimated(0.f); - scrollbar_controller_->DidScrollBegin(); base::TimeTicks time; time += base::TimeDelta::FromSeconds(100); scrollbar_controller_->Animate(time); EXPECT_FLOAT_EQ(0.0f, scrollbar_layer_->Opacity()); - scrollbar_controller_->DidScrollEnd(); EXPECT_TRUE(start_fade_.is_null()); @@ -1473,11 +1409,9 @@ TEST_F(ScrollbarAnimationControllerAndroidTest, HideOnResize) { GetScrollNode(scroll_layer_)->container_bounds = gfx::Size(100, 200); scroll_layer_->UpdateScrollable(); UpdateActiveTreeDrawProperties(); - scrollbar_controller_->DidScrollBegin(); scrollbar_controller_->DidScrollUpdate(); EXPECT_FLOAT_EQ(1.0f, scrollbar_layer_->Opacity()); - scrollbar_controller_->DidScrollEnd(); // Shrink along Y axis and expand along X, horizontal scrollbar // should disappear. @@ -1485,12 +1419,8 @@ TEST_F(ScrollbarAnimationControllerAndroidTest, HideOnResize) { scroll_layer_->UpdateScrollable(); UpdateActiveTreeDrawProperties(); - scrollbar_controller_->DidScrollBegin(); - scrollbar_controller_->DidScrollUpdate(); EXPECT_FLOAT_EQ(0.0f, scrollbar_layer_->Opacity()); - - scrollbar_controller_->DidScrollEnd(); } TEST_F(VerticalScrollbarAnimationControllerAndroidTest, HideOnResize) { @@ -1502,23 +1432,17 @@ TEST_F(VerticalScrollbarAnimationControllerAndroidTest, HideOnResize) { GetScrollNode(scroll_layer_)->container_bounds = gfx::Size(100, 200); scroll_layer_->UpdateScrollable(); UpdateActiveTreeDrawProperties(); - scrollbar_controller_->DidScrollBegin(); scrollbar_controller_->DidScrollUpdate(); EXPECT_FLOAT_EQ(0.0f, scrollbar_layer_->Opacity()); - scrollbar_controller_->DidScrollEnd(); // Shrink along Y axis and expand along X, vertical scrollbar should appear. GetScrollNode(scroll_layer_)->container_bounds = gfx::Size(200, 100); scroll_layer_->UpdateScrollable(); UpdateActiveTreeDrawProperties(); - scrollbar_controller_->DidScrollBegin(); - scrollbar_controller_->DidScrollUpdate(); EXPECT_FLOAT_EQ(1.0f, scrollbar_layer_->Opacity()); - - scrollbar_controller_->DidScrollEnd(); } TEST_F(ScrollbarAnimationControllerAndroidTest, HideOnUserNonScrollableHorz) { @@ -1527,12 +1451,8 @@ TEST_F(ScrollbarAnimationControllerAndroidTest, HideOnUserNonScrollableHorz) { GetScrollNode(scroll_layer_)->user_scrollable_horizontal = false; UpdateActiveTreeDrawProperties(); - scrollbar_controller_->DidScrollBegin(); - scrollbar_controller_->DidScrollUpdate(); EXPECT_FLOAT_EQ(0.0f, scrollbar_layer_->Opacity()); - - scrollbar_controller_->DidScrollEnd(); } TEST_F(ScrollbarAnimationControllerAndroidTest, ShowOnUserNonScrollableVert) { @@ -1541,12 +1461,8 @@ TEST_F(ScrollbarAnimationControllerAndroidTest, ShowOnUserNonScrollableVert) { GetScrollNode(scroll_layer_)->user_scrollable_vertical = false; UpdateActiveTreeDrawProperties(); - scrollbar_controller_->DidScrollBegin(); - scrollbar_controller_->DidScrollUpdate(); EXPECT_FLOAT_EQ(1.0f, scrollbar_layer_->Opacity()); - - scrollbar_controller_->DidScrollEnd(); } TEST_F(VerticalScrollbarAnimationControllerAndroidTest, @@ -1556,12 +1472,8 @@ TEST_F(VerticalScrollbarAnimationControllerAndroidTest, GetScrollNode(scroll_layer_)->user_scrollable_vertical = false; UpdateActiveTreeDrawProperties(); - scrollbar_controller_->DidScrollBegin(); - scrollbar_controller_->DidScrollUpdate(); EXPECT_FLOAT_EQ(0.0f, scrollbar_layer_->Opacity()); - - scrollbar_controller_->DidScrollEnd(); } TEST_F(VerticalScrollbarAnimationControllerAndroidTest, @@ -1571,32 +1483,26 @@ TEST_F(VerticalScrollbarAnimationControllerAndroidTest, GetScrollNode(scroll_layer_)->user_scrollable_horizontal = false; UpdateActiveTreeDrawProperties(); - scrollbar_controller_->DidScrollBegin(); - scrollbar_controller_->DidScrollUpdate(); EXPECT_FLOAT_EQ(1.0f, scrollbar_layer_->Opacity()); - - scrollbar_controller_->DidScrollEnd(); } TEST_F(ScrollbarAnimationControllerAndroidTest, AwakenByScrollingGesture) { base::TimeTicks time; time += base::TimeDelta::FromSeconds(1); - scrollbar_controller_->DidScrollBegin(); EXPECT_FALSE(did_request_animate_); scrollbar_controller_->DidScrollUpdate(); EXPECT_FALSE(did_request_animate_); EXPECT_FLOAT_EQ(1.0f, scrollbar_layer_->Opacity()); - EXPECT_TRUE(start_fade_.is_null()); + EXPECT_FALSE(start_fade_.is_null()); time += base::TimeDelta::FromSeconds(100); scrollbar_controller_->Animate(time); EXPECT_FALSE(did_request_animate_); EXPECT_FLOAT_EQ(1.0f, scrollbar_layer_->Opacity()); - scrollbar_controller_->DidScrollEnd(); EXPECT_FALSE(did_request_animate_); std::move(start_fade_).Run(); EXPECT_TRUE(did_request_animate_); @@ -1622,9 +1528,7 @@ TEST_F(ScrollbarAnimationControllerAndroidTest, AwakenByScrollingGesture) { time += base::TimeDelta::FromSeconds(1); - scrollbar_controller_->DidScrollBegin(); scrollbar_controller_->DidScrollUpdate(); - scrollbar_controller_->DidScrollEnd(); std::move(start_fade_).Run(); EXPECT_TRUE(did_request_animate_); @@ -1743,7 +1647,6 @@ TEST_F(ScrollbarAnimationControllerAndroidTest, did_request_animate_ = false; EXPECT_FLOAT_EQ(2.0f / 3.0f, scrollbar_layer_->Opacity()); - scrollbar_controller_->DidScrollBegin(); EXPECT_FALSE(did_request_animate_); EXPECT_FLOAT_EQ(2.0f / 3.0f, scrollbar_layer_->Opacity()); @@ -1753,7 +1656,6 @@ TEST_F(ScrollbarAnimationControllerAndroidTest, did_request_animate_ = false; EXPECT_FLOAT_EQ(1.0f / 3.0f, scrollbar_layer_->Opacity()); - scrollbar_controller_->DidScrollEnd(); EXPECT_FALSE(did_request_animate_); EXPECT_FLOAT_EQ(1.0f / 3.0f, scrollbar_layer_->Opacity()); @@ -1783,7 +1685,6 @@ TEST_F(ScrollbarAnimationControllerAndroidTest, did_request_animate_ = false; EXPECT_FLOAT_EQ(2.0f / 3.0f, scrollbar_layer_->Opacity()); - scrollbar_controller_->DidScrollBegin(); EXPECT_FLOAT_EQ(2.0f / 3.0f, scrollbar_layer_->Opacity()); time += base::TimeDelta::FromSeconds(1); @@ -1798,10 +1699,10 @@ TEST_F(ScrollbarAnimationControllerAndroidTest, EXPECT_FLOAT_EQ(1, scrollbar_layer_->Opacity()); time += base::TimeDelta::FromSeconds(1); - scrollbar_controller_->DidScrollEnd(); EXPECT_FALSE(did_request_animate_); EXPECT_FLOAT_EQ(1, scrollbar_layer_->Opacity()); } } // namespace + } // namespace cc diff --git a/chromium/cc/input/scrollbar_controller.cc b/chromium/cc/input/scrollbar_controller.cc index aac1d4ceccd..490cd44d1d4 100644 --- a/chromium/cc/input/scrollbar_controller.cc +++ b/chromium/cc/input/scrollbar_controller.cc @@ -8,6 +8,7 @@ #include "base/cancelable_callback.h" #include "cc/base/math_util.h" +#include "cc/input/scroll_utils.h" #include "cc/input/scrollbar.h" #include "cc/input/scrollbar_controller.h" #include "cc/trees/layer_tree_impl.h" @@ -35,7 +36,7 @@ void ScrollbarController::WillBeginImplFrame() { } // Retrieves the ScrollbarLayerImplBase corresponding to the stashed ElementId. -ScrollbarLayerImplBase* ScrollbarController::ScrollbarLayer() { +ScrollbarLayerImplBase* ScrollbarController::ScrollbarLayer() const { if (!captured_scrollbar_metadata_.has_value()) return nullptr; @@ -52,7 +53,7 @@ ScrollbarLayerImplBase* ScrollbarController::ScrollbarLayer() { // GSU. InputHandlerPointerResult ScrollbarController::HandlePointerDown( const gfx::PointF position_in_widget, - bool shift_modifier) { + bool jump_key_modifier) { LayerImpl* layer_impl = GetLayerHitByPoint(position_in_widget); // If a non-custom scrollbar layer was not found, we return early as there is @@ -88,12 +89,15 @@ InputHandlerPointerResult ScrollbarController::HandlePointerDown( scroll_result.type = PointerResultType::kScrollbarScroll; layer_tree_host_impl_->active_tree()->UpdateScrollbarGeometries(); const ScrollbarPart scrollbar_part = - GetScrollbarPartFromPointerDown(scrollbar, position_in_widget); + GetScrollbarPartFromPointerDown(position_in_widget); + const bool perform_jump_click_on_track = + scrollbar->JumpOnTrackClick() != jump_key_modifier; scroll_result.scroll_offset = GetScrollOffsetForScrollbarPart( - scrollbar, scrollbar_part, shift_modifier); + scrollbar_part, perform_jump_click_on_track); last_known_pointer_position_ = position_in_widget; scrollbar_scroll_is_active_ = true; - scroll_result.scroll_units = Granularity(scrollbar_part, shift_modifier); + scroll_result.scroll_units = + Granularity(scrollbar_part, perform_jump_click_on_track); if (scrollbar_part == ScrollbarPart::THUMB) { drag_state_ = DragState(); drag_state_->drag_origin = position_in_widget; @@ -112,12 +116,12 @@ InputHandlerPointerResult ScrollbarController::HandlePointerDown( // have the potential of initiating an autoscroll (if held down for long // enough). DCHECK(scrollbar_part != ScrollbarPart::THUMB); - cancelable_autoscroll_task_ = std::make_unique<base::CancelableOnceClosure>( - base::BindOnce(&ScrollbarController::StartAutoScrollAnimation, - base::Unretained(this), - InitialDeltaToAutoscrollVelocity( - scrollbar, scroll_result.scroll_offset), - scrollbar, scrollbar_part)); + cancelable_autoscroll_task_ = + std::make_unique<base::CancelableOnceClosure>(base::BindOnce( + &ScrollbarController::StartAutoScrollAnimation, + base::Unretained(this), + InitialDeltaToAutoscrollVelocity(scroll_result.scroll_offset), + scrollbar_part)); layer_tree_host_impl_->task_runner_provider() ->ImplThreadTaskRunner() ->PostDelayedTask(FROM_HERE, cancelable_autoscroll_task_->callback(), @@ -127,16 +131,16 @@ InputHandlerPointerResult ScrollbarController::HandlePointerDown( } bool ScrollbarController::SnapToDragOrigin( - const ScrollbarLayerImplBase* scrollbar, - const gfx::PointF pointer_position_in_widget) { + const gfx::PointF pointer_position_in_widget) const { // Consult the ScrollbarTheme to check if thumb snapping is supported on the // current platform. + const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer(); if (!(scrollbar && scrollbar->SupportsDragSnapBack())) return false; bool clipped = false; - const gfx::PointF pointer_position_in_layer = GetScrollbarRelativePosition( - scrollbar, pointer_position_in_widget, &clipped); + const gfx::PointF pointer_position_in_layer = + GetScrollbarRelativePosition(pointer_position_in_widget, &clipped); if (clipped) return false; @@ -189,10 +193,10 @@ bool ScrollbarController::SnapToDragOrigin( ui::ScrollGranularity ScrollbarController::Granularity( const ScrollbarPart scrollbar_part, - const bool shift_modifier) { + const bool jump_key_modifier) const { const bool shift_click_on_scrollbar_track = - shift_modifier && (scrollbar_part == ScrollbarPart::FORWARD_TRACK || - scrollbar_part == ScrollbarPart::BACK_TRACK); + jump_key_modifier && (scrollbar_part == ScrollbarPart::FORWARD_TRACK || + scrollbar_part == ScrollbarPart::BACK_TRACK); if (shift_click_on_scrollbar_track || scrollbar_part == ScrollbarPart::THUMB) return ui::ScrollGranularity::kScrollByPrecisePixel; @@ -201,17 +205,17 @@ ui::ScrollGranularity ScrollbarController::Granularity( return ui::ScrollGranularity::kScrollByPixel; } -float ScrollbarController::GetScrollDeltaForAbsoluteJump( - const ScrollbarLayerImplBase* scrollbar) { +float ScrollbarController::GetScrollDeltaForAbsoluteJump() const { layer_tree_host_impl_->active_tree()->UpdateScrollbarGeometries(); bool clipped = false; - const gfx::PointF pointer_position_in_layer = GetScrollbarRelativePosition( - scrollbar, last_known_pointer_position_, &clipped); + const gfx::PointF pointer_position_in_layer = + GetScrollbarRelativePosition(last_known_pointer_position_, &clipped); if (clipped) return 0; + const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer(); const float pointer_location = scrollbar->orientation() == ScrollbarOrientation::VERTICAL ? pointer_position_in_layer.y() @@ -232,19 +236,18 @@ float ScrollbarController::GetScrollDeltaForAbsoluteJump( const float delta = round(std::abs(desired_thumb_origin - current_thumb_origin)); - return delta * GetScrollerToScrollbarRatio(scrollbar); + return delta * GetScrollerToScrollbarRatio(); } int ScrollbarController::GetScrollDeltaForDragPosition( - const ScrollbarLayerImplBase* scrollbar, - const gfx::PointF pointer_position_in_widget) { + const gfx::PointF pointer_position_in_widget) const { + const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer(); const float pointer_delta = scrollbar->orientation() == ScrollbarOrientation::VERTICAL ? pointer_position_in_widget.y() - drag_state_->drag_origin.y() : pointer_position_in_widget.x() - drag_state_->drag_origin.x(); - const float new_offset = - pointer_delta * GetScrollerToScrollbarRatio(scrollbar); + const float new_offset = pointer_delta * GetScrollerToScrollbarRatio(); const float scroll_delta = drag_state_->scroll_position_at_start_ + new_offset - scrollbar->current_pos(); @@ -276,7 +279,7 @@ InputHandlerPointerResult ScrollbarController::HandlePointerMove( if (drag_processed_for_current_frame_) return scroll_result; - if (SnapToDragOrigin(scrollbar, position_in_widget)) { + if (SnapToDragOrigin(position_in_widget)) { const float delta = scrollbar->current_pos() - drag_state_->scroll_position_at_start_; scroll_result.scroll_units = ui::ScrollGranularity::kScrollByPrecisePixel; @@ -302,7 +305,7 @@ InputHandlerPointerResult ScrollbarController::HandlePointerMove( // valid ScrollNode. DCHECK(target_node); - int delta = GetScrollDeltaForDragPosition(scrollbar, position_in_widget); + int delta = GetScrollDeltaForDragPosition(position_in_widget); if (drag_state_->scroller_length_at_previous_move != scrollbar->scroll_layer_length()) { drag_state_->scroller_displacement = delta; @@ -339,8 +342,7 @@ InputHandlerPointerResult ScrollbarController::HandlePointerMove( return scroll_result; } -float ScrollbarController::GetScrollerToScrollbarRatio( - const ScrollbarLayerImplBase* scrollbar) { +float ScrollbarController::GetScrollerToScrollbarRatio() const { // Calculating the delta by which the scroller layer should move when // dragging the thumb depends on the following factors: // - scrollbar_track_length @@ -381,6 +383,7 @@ float ScrollbarController::GetScrollerToScrollbarRatio( // |<- scrollbar_thumb_length ->| // layer_tree_host_impl_->active_tree()->UpdateScrollbarGeometries(); + const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer(); float scroll_layer_length = scrollbar->scroll_layer_length(); float scrollbar_track_length = scrollbar->TrackLength(); gfx::Rect thumb_rect(scrollbar->ComputeThumbQuadRect()); @@ -388,7 +391,7 @@ float ScrollbarController::GetScrollerToScrollbarRatio( scrollbar->orientation() == ScrollbarOrientation::VERTICAL ? thumb_rect.height() : thumb_rect.width(); - int viewport_length = GetViewportLength(scrollbar); + int viewport_length = GetViewportLength(); return (scroll_layer_length - viewport_length) / (scrollbar_track_length - scrollbar_thumb_length); @@ -399,11 +402,18 @@ void ScrollbarController::ResetState() { drag_state_ = base::nullopt; autoscroll_state_ = base::nullopt; captured_scrollbar_metadata_ = base::nullopt; + if (cancelable_autoscroll_task_) { + cancelable_autoscroll_task_->Cancel(); + cancelable_autoscroll_task_.reset(); + } } -void ScrollbarController::DidUnregisterScrollbar(ElementId element_id) { +void ScrollbarController::DidUnregisterScrollbar( + ElementId element_id, + ScrollbarOrientation orientation) { if (captured_scrollbar_metadata_.has_value() && - captured_scrollbar_metadata_->scroll_element_id == element_id) + captured_scrollbar_metadata_->scroll_element_id == element_id && + captured_scrollbar_metadata_->orientation == orientation) ResetState(); } @@ -413,12 +423,10 @@ void ScrollbarController::RecomputeAutoscrollStateIfNeeded() { return; layer_tree_host_impl_->active_tree()->UpdateScrollbarGeometries(); - const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer(); - const gfx::Rect thumb_quad = scrollbar->ComputeThumbQuadRect(); bool clipped; - gfx::PointF scroller_relative_position(GetScrollbarRelativePosition( - scrollbar, last_known_pointer_position_, &clipped)); + gfx::PointF scroller_relative_position( + GetScrollbarRelativePosition(last_known_pointer_position_, &clipped)); if (clipped) return; @@ -429,6 +437,8 @@ void ScrollbarController::RecomputeAutoscrollStateIfNeeded() { int thumb_start = 0; int thumb_end = 0; int pointer_position = 0; + const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer(); + const gfx::Rect thumb_quad = scrollbar->ComputeThumbQuadRect(); if (scrollbar->orientation() == ScrollbarOrientation::VERTICAL) { thumb_start = thumb_quad.y(); thumb_end = thumb_quad.y() + thumb_quad.height(); @@ -458,7 +468,7 @@ void ScrollbarController::RecomputeAutoscrollStateIfNeeded() { const float scroll_layer_length = scrollbar->scroll_layer_length(); if (autoscroll_state_->scroll_layer_length != scroll_layer_length) { layer_tree_host_impl_->mutator_host()->ScrollAnimationAbort(); - StartAutoScrollAnimation(autoscroll_state_->velocity, scrollbar, + StartAutoScrollAnimation(autoscroll_state_->velocity, autoscroll_state_->pressed_scrollbar_part); } } @@ -466,8 +476,8 @@ void ScrollbarController::RecomputeAutoscrollStateIfNeeded() { // The animations need to be aborted/restarted based on the pointer location // (i.e leaving/entering the track/arrows, reaching the track end etc). The // autoscroll_state_ however, needs to be reset on pointer changes. - const gfx::RectF scrollbar_part_rect(GetRectForScrollbarPart( - scrollbar, autoscroll_state_->pressed_scrollbar_part)); + const gfx::RectF scrollbar_part_rect( + GetRectForScrollbarPart(autoscroll_state_->pressed_scrollbar_part)); if (!scrollbar_part_rect.Contains(scroller_relative_position)) { // Stop animating if pointer moves outside the rect bounds. layer_tree_host_impl_->mutator_host()->ScrollAnimationAbort(); @@ -475,17 +485,17 @@ void ScrollbarController::RecomputeAutoscrollStateIfNeeded() { !layer_tree_host_impl_->mutator_host()->IsElementAnimating( scrollbar->scroll_element_id())) { // Start animating if pointer re-enters the bounds. - StartAutoScrollAnimation(autoscroll_state_->velocity, scrollbar, + StartAutoScrollAnimation(autoscroll_state_->velocity, autoscroll_state_->pressed_scrollbar_part); } } // Helper to calculate the autoscroll velocity. float ScrollbarController::InitialDeltaToAutoscrollVelocity( - const ScrollbarLayerImplBase* scrollbar, gfx::ScrollOffset scroll_offset) const { + DCHECK(captured_scrollbar_metadata_.has_value()); const float scroll_delta = - scrollbar->orientation() == ScrollbarOrientation::VERTICAL + ScrollbarLayer()->orientation() == ScrollbarOrientation::VERTICAL ? scroll_offset.y() : scroll_offset.x(); return scroll_delta * kAutoscrollMultiplier; @@ -493,15 +503,17 @@ float ScrollbarController::InitialDeltaToAutoscrollVelocity( void ScrollbarController::StartAutoScrollAnimation( const float velocity, - const ScrollbarLayerImplBase* scrollbar, ScrollbarPart pressed_scrollbar_part) { // Autoscroll and thumb drag are mutually exclusive. Both can't be active at // the same time. DCHECK(!drag_state_.has_value()); + DCHECK(captured_scrollbar_metadata_.has_value()); DCHECK_NE(velocity, 0); + DCHECK(ScrollbarLayer()); // scroll_node is set up while handling GSB. If there's no node to scroll, we // don't need to create any animation for it. + const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer(); ScrollTree& scroll_tree = layer_tree_host_impl_->active_tree()->property_trees()->scroll_tree; ScrollNode* scroll_node = @@ -552,18 +564,13 @@ InputHandlerPointerResult ScrollbarController::HandlePointerUp( if (autoscroll_state_.has_value()) layer_tree_host_impl_->mutator_host()->ScrollAnimationAbort(); - if (cancelable_autoscroll_task_) { - cancelable_autoscroll_task_->Cancel(); - cancelable_autoscroll_task_.reset(); - } - ResetState(); return scroll_result; } // Returns the layer that is hit by the position_in_widget. LayerImpl* ScrollbarController::GetLayerHitByPoint( - const gfx::PointF position_in_widget) { + const gfx::PointF position_in_widget) const { LayerTreeImpl* active_tree = layer_tree_host_impl_->active_tree(); gfx::Point viewport_point(position_in_widget.x(), position_in_widget.y()); @@ -575,8 +582,8 @@ LayerImpl* ScrollbarController::GetLayerHitByPoint( return layer_impl; } -int ScrollbarController::GetViewportLength( - const ScrollbarLayerImplBase* scrollbar) const { +int ScrollbarController::GetViewportLength() const { + const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer(); const ScrollNode* scroll_node = layer_tree_host_impl_->active_tree() ->property_trees() @@ -587,30 +594,51 @@ int ScrollbarController::GetViewportLength( : scroll_node->container_bounds.width(); } +int ScrollbarController::GetScrollDeltaForPercentBasedScroll() const { + const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer(); + + const ScrollNode* scroll_node = + layer_tree_host_impl_->active_tree() + ->property_trees() + ->scroll_tree.FindNodeFromElementId(scrollbar->scroll_element_id()); + DCHECK(scroll_node); + + const gfx::Vector2dF scroll_delta = + scrollbar->orientation() == ScrollbarOrientation::VERTICAL + ? gfx::Vector2dF(0, kPercentDeltaForDirectionalScroll) + : gfx::Vector2dF(kPercentDeltaForDirectionalScroll, 0); + + const gfx::Vector2dF pixel_delta = + layer_tree_host_impl_->ResolveScrollGranularityToPixels( + *scroll_node, scroll_delta, + ui::ScrollGranularity::kScrollByPercentage); + + return scrollbar->orientation() == ScrollbarOrientation::VERTICAL + ? pixel_delta.y() + : pixel_delta.x(); +} + int ScrollbarController::GetScrollDeltaForScrollbarPart( - const ScrollbarLayerImplBase* scrollbar, const ScrollbarPart scrollbar_part, - const bool shift_modifier) { + const bool jump_key_modifier) const { int scroll_delta = 0; switch (scrollbar_part) { case ScrollbarPart::BACK_BUTTON: case ScrollbarPart::FORWARD_BUTTON: if (layer_tree_host_impl_->settings().percent_based_scrolling) { - scroll_delta = - kPercentDeltaForDirectionalScroll * GetViewportLength(scrollbar); + scroll_delta = GetScrollDeltaForPercentBasedScroll(); } else { scroll_delta = kPixelsPerLineStep * ScreenSpaceScaleFactor(); } break; case ScrollbarPart::BACK_TRACK: case ScrollbarPart::FORWARD_TRACK: { - if (shift_modifier) { - scroll_delta = GetScrollDeltaForAbsoluteJump(scrollbar); + if (jump_key_modifier) { + scroll_delta = GetScrollDeltaForAbsoluteJump(); break; } - scroll_delta = - GetViewportLength(scrollbar) * kMinFractionToStepWhenPaging; + scroll_delta = GetViewportLength() * kMinFractionToStepWhenPaging; break; } default: @@ -634,9 +662,8 @@ float ScrollbarController::ScreenSpaceScaleFactor() const { } gfx::PointF ScrollbarController::GetScrollbarRelativePosition( - const ScrollbarLayerImplBase* scrollbar, const gfx::PointF position_in_widget, - bool* clipped) { + bool* clipped) const { gfx::Transform inverse_screen_space_transform( gfx::Transform::kSkipInitialization); @@ -647,7 +674,7 @@ gfx::PointF ScrollbarController::GetScrollbarRelativePosition( ? 1.f / layer_tree_host_impl_->active_tree()->device_scale_factor() : 1.f; gfx::Transform scaled_screen_space_transform( - scrollbar->ScreenSpaceTransform()); + ScrollbarLayer()->ScreenSpaceTransform()); scaled_screen_space_transform.PostScale(scale, scale); if (!scaled_screen_space_transform.GetInverse( &inverse_screen_space_transform)) @@ -659,14 +686,14 @@ gfx::PointF ScrollbarController::GetScrollbarRelativePosition( // Determines the ScrollbarPart based on the position_in_widget. ScrollbarPart ScrollbarController::GetScrollbarPartFromPointerDown( - const ScrollbarLayerImplBase* scrollbar, - const gfx::PointF position_in_widget) { + const gfx::PointF position_in_widget) const { // position_in_widget needs to be transformed and made relative to the // scrollbar layer because hit testing assumes layer relative coordinates. + const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer(); bool clipped = false; const gfx::PointF scroller_relative_position( - GetScrollbarRelativePosition(scrollbar, position_in_widget, &clipped)); + GetScrollbarRelativePosition(position_in_widget, &clipped)); if (clipped) return ScrollbarPart::NO_PART; @@ -676,8 +703,8 @@ ScrollbarPart ScrollbarController::GetScrollbarPartFromPointerDown( // Determines the corresponding rect for the given scrollbar part. gfx::Rect ScrollbarController::GetRectForScrollbarPart( - const ScrollbarLayerImplBase* scrollbar, - const ScrollbarPart scrollbar_part) { + const ScrollbarPart scrollbar_part) const { + const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer(); if (scrollbar_part == ScrollbarPart::BACK_BUTTON) return scrollbar->BackButtonRect(); if (scrollbar_part == ScrollbarPart::FORWARD_BUTTON) @@ -692,11 +719,11 @@ gfx::Rect ScrollbarController::GetRectForScrollbarPart( // Determines the scroll offsets based on the ScrollbarPart and the scrollbar // orientation. gfx::ScrollOffset ScrollbarController::GetScrollOffsetForScrollbarPart( - const ScrollbarLayerImplBase* scrollbar, const ScrollbarPart scrollbar_part, - const bool shift_modifier) { + const bool jump_key_modifier) const { + const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer(); float scroll_delta = - GetScrollDeltaForScrollbarPart(scrollbar, scrollbar_part, shift_modifier); + GetScrollDeltaForScrollbarPart(scrollbar_part, jump_key_modifier); // See CreateScrollStateForGesture for more information on how these values // will be interpreted. diff --git a/chromium/cc/input/scrollbar_controller.h b/chromium/cc/input/scrollbar_controller.h index 4177c3464e5..ebe4dde9f09 100644 --- a/chromium/cc/input/scrollbar_controller.h +++ b/chromium/cc/input/scrollbar_controller.h @@ -119,24 +119,31 @@ class CC_EXPORT ScrollbarController { explicit ScrollbarController(LayerTreeHostImpl*); virtual ~ScrollbarController(); + // On Mac, the "jump to the spot that's clicked" setting can be dynamically + // set via System Preferences. When enabled, the expectation is that regular + // clicks on the scrollbar should make the scroller "jump" to the clicked + // location rather than animated scrolling. Additionally, when this is enabled + // and the user does an Option + click on the scrollbar, the scroller should + // *not* jump to that spot (i.e it should be treated as a regular track + // click). When this setting is disabled on the Mac, Option + click should + // make the scroller jump and a regular click should animate the scroll + // offset. On all other platforms, the "jump on click" option is available + // (via Shift + click) but is not configurable. InputHandlerPointerResult HandlePointerDown( const gfx::PointF position_in_widget, - const bool shift_modifier); + const bool jump_key_modifier); InputHandlerPointerResult HandlePointerMove( const gfx::PointF position_in_widget); InputHandlerPointerResult HandlePointerUp( const gfx::PointF position_in_widget); - // "velocity" here is calculated based on the initial scroll delta (See - // InitialDeltaToAutoscrollVelocity). This value carries a "sign" which is - // needed to determine whether we should set up the autoscrolling in the - // forwards or the backwards direction. - void StartAutoScrollAnimation(float velocity, - const ScrollbarLayerImplBase* scrollbar, - ScrollbarPart pressed_scrollbar_part); - bool ScrollbarScrollIsActive() { return scrollbar_scroll_is_active_; } - void DidUnregisterScrollbar(ElementId element_id); - ScrollbarLayerImplBase* ScrollbarLayer(); + bool AutoscrollTaskIsScheduled() const { + return cancelable_autoscroll_task_ != nullptr; + } + bool ScrollbarScrollIsActive() const { return scrollbar_scroll_is_active_; } + void DidUnregisterScrollbar(ElementId element_id, + ScrollbarOrientation orientation); + ScrollbarLayerImplBase* ScrollbarLayer() const; void WillBeginImplFrame(); void ResetState(); @@ -187,44 +194,42 @@ class CC_EXPORT ScrollbarController { ScrollbarOrientation orientation; }; + // "velocity" here is calculated based on the initial scroll delta (See + // InitialDeltaToAutoscrollVelocity). This value carries a "sign" which is + // needed to determine whether we should set up the autoscrolling in the + // forwards or the backwards direction. + void StartAutoScrollAnimation(float velocity, + ScrollbarPart pressed_scrollbar_part); + // Returns the DSF based on whether use-zoom-for-dsf is enabled. float ScreenSpaceScaleFactor() const; // Helper to convert scroll offset to autoscroll velocity. - float InitialDeltaToAutoscrollVelocity( - const ScrollbarLayerImplBase* scrollbar, - gfx::ScrollOffset scroll_offset) const; + float InitialDeltaToAutoscrollVelocity(gfx::ScrollOffset scroll_offset) const; // Returns the hit tested ScrollbarPart based on the position_in_widget. ScrollbarPart GetScrollbarPartFromPointerDown( - const ScrollbarLayerImplBase* scrollbar, - const gfx::PointF position_in_widget); + const gfx::PointF position_in_widget) const; // Returns scroll offsets based on which ScrollbarPart was hit tested. gfx::ScrollOffset GetScrollOffsetForScrollbarPart( - const ScrollbarLayerImplBase* scrollbar, const ScrollbarPart scrollbar_part, - const bool shift_modifier); + const bool jump_key_modifier) const; // Returns the rect for the ScrollbarPart. - gfx::Rect GetRectForScrollbarPart(const ScrollbarLayerImplBase* scrollbar, - const ScrollbarPart scrollbar_part); + gfx::Rect GetRectForScrollbarPart(const ScrollbarPart scrollbar_part) const; - LayerImpl* GetLayerHitByPoint(const gfx::PointF position_in_widget); - int GetScrollDeltaForScrollbarPart(const ScrollbarLayerImplBase* scrollbar, - const ScrollbarPart scrollbar_part, - const bool shift_modifier); + LayerImpl* GetLayerHitByPoint(const gfx::PointF position_in_widget) const; + int GetScrollDeltaForScrollbarPart(const ScrollbarPart scrollbar_part, + const bool jump_key_modifier) const; // Makes position_in_widget relative to the scrollbar. - gfx::PointF GetScrollbarRelativePosition( - const ScrollbarLayerImplBase* scrollbar, - const gfx::PointF position_in_widget, - bool* clipped); + gfx::PointF GetScrollbarRelativePosition(const gfx::PointF position_in_widget, + bool* clipped) const; // Decides if the scroller should snap to the offset that it was originally at // (i.e the offset before the thumb drag). - bool SnapToDragOrigin(const ScrollbarLayerImplBase* scrollbar, - const gfx::PointF pointer_position_in_widget); + bool SnapToDragOrigin(const gfx::PointF pointer_position_in_widget) const; // Decides whether a track autoscroll should be aborted (or restarted) due to // the thumb reaching the pointer or the pointer leaving (or re-entering) the @@ -233,22 +238,24 @@ class CC_EXPORT ScrollbarController { // Shift (or "Option" in case of Mac) + click is expected to do a non-animated // jump to a certain offset. - float GetScrollDeltaForAbsoluteJump(const ScrollbarLayerImplBase* scrollbar); + float GetScrollDeltaForAbsoluteJump() const; // Determines if the delta needs to be animated. ui::ScrollGranularity Granularity(const ScrollbarPart scrollbar_part, - bool shift_modifier); + bool jump_key_modifier) const; // Calculates the delta based on position_in_widget and drag_origin. int GetScrollDeltaForDragPosition( - const ScrollbarLayerImplBase* scrollbar, - const gfx::PointF pointer_position_in_widget); + const gfx::PointF pointer_position_in_widget) const; // Returns the ratio of the scroller length to the scrollbar length. This is // needed to scale the scroll delta for thumb drag. - float GetScrollerToScrollbarRatio(const ScrollbarLayerImplBase* scrollbar); + float GetScrollerToScrollbarRatio() const; + + int GetViewportLength() const; - int GetViewportLength(const ScrollbarLayerImplBase* scrollbar) const; + // Returns the pixel delta for a percent-based scroll of the scrollbar + int GetScrollDeltaForPercentBasedScroll() const; LayerTreeHostImpl* layer_tree_host_impl_; diff --git a/chromium/cc/input/touch_action.h b/chromium/cc/input/touch_action.h index f00d263acea..e873bd02d62 100644 --- a/chromium/cc/input/touch_action.h +++ b/chromium/cc/input/touch_action.h @@ -8,7 +8,7 @@ #include <cstdlib> #include <string> -#include "base/logging.h" +#include "base/notreached.h" namespace cc { diff --git a/chromium/cc/layers/deadline_policy.h b/chromium/cc/layers/deadline_policy.h index 6c3b0063046..6a461e3c235 100644 --- a/chromium/cc/layers/deadline_policy.h +++ b/chromium/cc/layers/deadline_policy.h @@ -7,7 +7,7 @@ #include <cstdint> -#include "base/logging.h" +#include "base/check.h" #include "base/optional.h" #include "cc/cc_export.h" diff --git a/chromium/cc/layers/draw_properties.h b/chromium/cc/layers/draw_properties.h index b241728724c..d5a0a7d84a4 100644 --- a/chromium/cc/layers/draw_properties.h +++ b/chromium/cc/layers/draw_properties.h @@ -53,9 +53,9 @@ struct CC_EXPORT DrawProperties { // the layer's coordinate space. gfx::Rect visible_layer_rect; - // In target surface space, the rect that encloses the clipped, drawable - // content of the layer. - gfx::Rect drawable_content_rect; + // In target surface space, the rect that encloses the clipped, visible, + // and drawable content of the layer. + gfx::Rect visible_drawable_content_rect; // In target surface space, the original rect that clipped this layer. This // value is used to avoid unnecessarily changing GL scissor state. diff --git a/chromium/cc/layers/heads_up_display_layer_impl.cc b/chromium/cc/layers/heads_up_display_layer_impl.cc index 6851763975f..b539a5da7c7 100644 --- a/chromium/cc/layers/heads_up_display_layer_impl.cc +++ b/chromium/cc/layers/heads_up_display_layer_impl.cc @@ -10,6 +10,7 @@ #include <algorithm> #include <vector> +#include "base/logging.h" #include "base/memory/shared_memory_mapping.h" #include "base/numerics/safe_conversions.h" #include "base/optional.h" @@ -19,6 +20,7 @@ #include "base/trace_event/trace_event.h" #include "base/trace_event/traced_value.h" #include "cc/debug/debug_colors.h" +#include "cc/metrics/dropped_frame_counter.h" #include "cc/paint/display_item_list.h" #include "cc/paint/paint_canvas.h" #include "cc/paint/paint_flags.h" @@ -27,7 +29,6 @@ #include "cc/paint/skia_paint_canvas.h" #include "cc/raster/scoped_gpu_raster.h" #include "cc/resources/memory_history.h" -#include "cc/trees/frame_rate_counter.h" #include "cc/trees/layer_tree_frame_sink.h" #include "cc/trees/layer_tree_host_impl.h" #include "cc/trees/layer_tree_impl.h" @@ -88,27 +89,9 @@ class DummyImageProvider : public ImageProvider { } // namespace -HeadsUpDisplayLayerImpl::Graph::Graph(double indicator_value, - double start_upper_bound) - : value(0.0), - min(0.0), - max(0.0), - current_upper_bound(start_upper_bound), - default_upper_bound(start_upper_bound), - indicator(indicator_value) {} - -double HeadsUpDisplayLayerImpl::Graph::UpdateUpperBound() { - double target_upper_bound = std::max(max, default_upper_bound); - current_upper_bound += (target_upper_bound - current_upper_bound) * 0.5; - return current_upper_bound; -} - HeadsUpDisplayLayerImpl::HeadsUpDisplayLayerImpl(LayerTreeImpl* tree_impl, int id) - : LayerImpl(tree_impl, id), - internal_contents_scale_(1.f), - fps_graph_(60.0, 80.0), - paint_time_graph_(16.0, 48.0) {} + : LayerImpl(tree_impl, id) {} HeadsUpDisplayLayerImpl::~HeadsUpDisplayLayerImpl() { ReleaseResources(); @@ -550,18 +533,8 @@ void HeadsUpDisplayLayerImpl::UpdateHudContents() { time_of_last_graph_update_ = now; if (debug_state.show_fps_counter) { - FrameRateCounter* fps_counter = layer_tree_impl()->frame_rate_counter(); - fps_graph_.value = fps_counter->GetAverageFPS(); - fps_counter->GetMinAndMaxFPS(&fps_graph_.min, &fps_graph_.max); - current_throughput_ = layer_tree_impl()->current_universal_throughput(); - if (current_throughput_.has_value()) { - if (!max_throughput.has_value() || - current_throughput_.value() > max_throughput.value()) - max_throughput = current_throughput_; - if (!min_throughput.has_value() || - current_throughput_.value() < min_throughput.value()) - min_throughput = current_throughput_; - } + throughput_value_ = + layer_tree_impl()->dropped_frame_counter()->GetAverageThroughput(); } if (debug_state.ShowMemoryStats()) { @@ -572,9 +545,6 @@ void HeadsUpDisplayLayerImpl::UpdateHudContents() { memory_entry_ = MemoryHistory::Entry(); } } - - fps_graph_.UpdateUpperBound(); - paint_time_graph_.UpdateUpperBound(); } void HeadsUpDisplayLayerImpl::DrawHudContents(PaintCanvas* canvas) { @@ -597,8 +567,8 @@ void HeadsUpDisplayLayerImpl::DrawHudContents(PaintCanvas* canvas) { return; } - SkRect area = - DrawFPSDisplay(canvas, layer_tree_impl()->frame_rate_counter(), 0, 0); + SkRect area = DrawFrameThroughputDisplay( + canvas, layer_tree_impl()->dropped_frame_counter(), 0, 0); area = DrawGpuRasterizationStatus(canvas, 0, area.bottom(), std::max<SkScalar>(area.width(), 150)); @@ -652,30 +622,18 @@ void HeadsUpDisplayLayerImpl::DrawGraphBackground(PaintCanvas* canvas, void HeadsUpDisplayLayerImpl::DrawGraphLines(PaintCanvas* canvas, PaintFlags* flags, - const SkRect& bounds, - const Graph& graph) const { + const SkRect& bounds) const { // Draw top and bottom line. flags->setColor(DebugColors::HUDSeparatorLineColor()); canvas->drawLine(bounds.left(), bounds.top() - 1, bounds.right(), bounds.top() - 1, *flags); canvas->drawLine(bounds.left(), bounds.bottom(), bounds.right(), bounds.bottom(), *flags); - - // Draw indicator line (additive blend mode to increase contrast when drawn on - // top of graph). - flags->setColor(DebugColors::HUDIndicatorLineColor()); - flags->setBlendMode(SkBlendMode::kPlus); - const double indicator_top = - bounds.height() * (1.0 - graph.indicator / graph.current_upper_bound) - - 1.0; - canvas->drawLine(bounds.left(), bounds.top() + indicator_top, bounds.right(), - bounds.top() + indicator_top, *flags); - flags->setBlendMode(SkBlendMode::kSrcOver); } -SkRect HeadsUpDisplayLayerImpl::DrawFPSDisplay( +SkRect HeadsUpDisplayLayerImpl::DrawFrameThroughputDisplay( PaintCanvas* canvas, - const FrameRateCounter* fps_counter, + const DroppedFrameCounter* dropped_frame_counter, int right, int top) const { const int kPadding = 4; @@ -685,39 +643,35 @@ SkRect HeadsUpDisplayLayerImpl::DrawFPSDisplay( const int kFontHeight = 12; const int kGraphWidth = - base::saturated_cast<int>(fps_counter->time_stamp_history_size()) - 2; + base::saturated_cast<int>(dropped_frame_counter->frame_history_size()); const int kGraphHeight = 40; - const int kHistogramWidth = 37; - - int width = kGraphWidth + kHistogramWidth + 4 * kPadding; - int height = - 2 * kTitleFontHeight + 2 * kFontHeight + kGraphHeight + 10 * kPadding + 2; + int width = kGraphWidth + 4 * kPadding; + int height = kTitleFontHeight + kFontHeight + kGraphHeight + 6 * kPadding + 2; int left = 0; SkRect area = SkRect::MakeXYWH(left, top, width, height); PaintFlags flags; DrawGraphBackground(canvas, &flags, area); - SkRect title_bounds = SkRect::MakeXYWH( - left + kPadding, top + kPadding, kGraphWidth + kHistogramWidth + kGap + 2, - kTitleFontHeight); + SkRect title_bounds = + SkRect::MakeXYWH(left + kPadding, top + kPadding, kGraphWidth + kGap + 2, + kTitleFontHeight); SkRect text_bounds = SkRect::MakeXYWH(left + kPadding, title_bounds.bottom() + 2 * kPadding, - kGraphWidth + kHistogramWidth + kGap + 2, kFontHeight); + kGraphWidth + kGap + 2, kFontHeight); SkRect graph_bounds = SkRect::MakeXYWH(left + kPadding, text_bounds.bottom() + 2 * kPadding, kGraphWidth, kGraphHeight); - SkRect histogram_bounds = - SkRect::MakeXYWH(graph_bounds.right() + kGap, graph_bounds.top(), - kHistogramWidth, kGraphHeight); // Draw the fps meter. - const std::string title("Frame Rate"); - const std::string value_text = - base::StringPrintf("%5.1f fps", fps_graph_.value); - const std::string min_max_text = - base::StringPrintf("%.0f-%.0f", fps_graph_.min, fps_graph_.max); + const std::string title("Frames"); + const std::string value_text = base::StringPrintf("%d %%", throughput_value_); + const std::string dropped_frames_text = + base::StringPrintf("%zu (%zu m) dropped of %zu", + dropped_frame_counter->total_compositor_dropped(), + dropped_frame_counter->total_main_dropped(), + dropped_frame_counter->total_frames()); VLOG(1) << value_text; @@ -728,105 +682,40 @@ SkRect HeadsUpDisplayLayerImpl::DrawFPSDisplay( flags.setColor(DebugColors::FPSDisplayTextAndGraphColor()); DrawText(canvas, flags, value_text, TextAlign::kLeft, kFontHeight, text_bounds.left(), text_bounds.bottom()); - DrawText(canvas, flags, min_max_text, TextAlign::kRight, kFontHeight, + DrawText(canvas, flags, dropped_frames_text, TextAlign::kRight, kFontHeight, text_bounds.right(), text_bounds.bottom()); - DrawGraphLines(canvas, &flags, graph_bounds, fps_graph_); - - // Draw the throughput meter. - int current_top = histogram_bounds.bottom() + kPadding; - const std::string throughput_title("Throughput"); - const std::string throughput_value_text = - current_throughput_.has_value() - ? base::StringPrintf("%d %%", current_throughput_.value()) - : base::StringPrintf("n/a"); - - VLOG(1) << throughput_value_text; - - flags.setColor(DebugColors::HUDTitleColor()); - DrawText(canvas, flags, throughput_title, TextAlign::kLeft, kTitleFontHeight, - title_bounds.left(), title_bounds.bottom() + current_top); - - flags.setColor(DebugColors::FPSDisplayTextAndGraphColor()); - DrawText(canvas, flags, throughput_value_text, TextAlign::kLeft, kFontHeight, - text_bounds.left(), text_bounds.bottom() + current_top); - if (min_throughput.has_value()) { - const std::string throughput_min_max_text = base::StringPrintf( - "%d-%d", min_throughput.value(), max_throughput.value()); - DrawText(canvas, flags, throughput_min_max_text, TextAlign::kRight, - kFontHeight, text_bounds.right(), - text_bounds.bottom() + current_top); - } - - // Collect fps graph and histogram data. - SkPath path; - - const int kHistogramSize = 20; - double histogram[kHistogramSize] = {1.0}; - double max_bucket_value = 1.0; - - for (FrameRateCounter::RingBufferType::Iterator it = --fps_counter->end(); it; - --it) { - base::TimeDelta delta = fps_counter->RecentFrameInterval(it.index() + 1); - - // Skip this particular instantaneous frame rate if it is not likely to have - // been valid. - if (!fps_counter->IsBadFrameInterval(delta)) { - double fps = 1.0 / delta.InSecondsF(); - - // Clamp the FPS to the range we want to plot visually. - double p = fps / fps_graph_.current_upper_bound; - if (p > 1.0) - p = 1.0; - - // Plot this data point. - SkPoint cur = - SkPoint::Make(graph_bounds.left() + it.index(), - graph_bounds.bottom() - p * graph_bounds.height()); - if (path.isEmpty()) - path.moveTo(cur); - else - path.lineTo(cur); - - // Use the fps value to find the right bucket in the histogram. - int bucket_index = floor(p * (kHistogramSize - 1)); - - // Add the delta time to take the time spent at that fps rate into - // account. - histogram[bucket_index] += delta.InSecondsF(); - max_bucket_value = std::max(histogram[bucket_index], max_bucket_value); - } - } - - // Draw FPS histogram. - flags.setColor(DebugColors::HUDSeparatorLineColor()); - canvas->drawLine(histogram_bounds.left() - 1, histogram_bounds.top() - 1, - histogram_bounds.left() - 1, histogram_bounds.bottom() + 1, - flags); - canvas->drawLine(histogram_bounds.right() + 1, histogram_bounds.top() - 1, - histogram_bounds.right() + 1, histogram_bounds.bottom() + 1, - flags); - - flags.setColor(DebugColors::FPSDisplayTextAndGraphColor()); - const double bar_height = histogram_bounds.height() / kHistogramSize; - - for (int i = kHistogramSize - 1; i >= 0; --i) { - if (histogram[i] > 0) { - double bar_width = - histogram[i] / max_bucket_value * histogram_bounds.width(); - canvas->drawRect( - SkRect::MakeXYWH(histogram_bounds.left(), - histogram_bounds.bottom() - (i + 1) * bar_height, - bar_width, 1), - flags); - } + DrawGraphLines(canvas, &flags, graph_bounds); + + // Collect the frames graph data. + SkPath good_path; + SkPath dropped_path; + SkPath partial_path; + for (auto it = --dropped_frame_counter->end(); it; --it) { + const auto state = **it; + int x = graph_bounds.left() + it.index(); + SkPath& path = state == DroppedFrameCounter::kFrameStateDropped + ? dropped_path + : state == DroppedFrameCounter::kFrameStateComplete + ? good_path + : partial_path; + path.moveTo(x, graph_bounds.top()); + path.lineTo(x, graph_bounds.bottom()); } // Draw FPS graph. flags.setAntiAlias(true); flags.setStyle(PaintFlags::kStroke_Style); flags.setStrokeWidth(1); - canvas->drawPath(path, flags); + + flags.setColor(SkColorSetA(SK_ColorGREEN, 128)); + canvas->drawPath(good_path, flags); + + flags.setColor(SkColorSetA(SK_ColorRED, 128)); + canvas->drawPath(dropped_path, flags); + + flags.setColor(SkColorSetA(SK_ColorYELLOW, 255)); + canvas->drawPath(partial_path, flags); return area; } diff --git a/chromium/cc/layers/heads_up_display_layer_impl.h b/chromium/cc/layers/heads_up_display_layer_impl.h index 323da1fff87..417d5a0d9b8 100644 --- a/chromium/cc/layers/heads_up_display_layer_impl.h +++ b/chromium/cc/layers/heads_up_display_layer_impl.h @@ -26,7 +26,7 @@ class ClientResourceProvider; } namespace cc { -class FrameRateCounter; +class DroppedFrameCounter; class LayerTreeFrameSink; class PaintCanvas; class PaintFlags; @@ -76,24 +76,6 @@ class CC_EXPORT HeadsUpDisplayLayerImpl : public LayerImpl { void PushPropertiesTo(LayerImpl* layer) override; private: - class Graph { - public: - Graph(double indicator_value, double start_upper_bound); - - // Eases the upper bound, which limits what is currently visible in the - // graph, so that the graph always scales to either it's max or - // default_upper_bound. - double UpdateUpperBound(); - - double value; - double min; - double max; - - double current_upper_bound; - const double default_upper_bound; - const double indicator; - }; - HeadsUpDisplayLayerImpl(LayerTreeImpl* tree_impl, int id); const char* LayerTypeAsString() const override; @@ -120,13 +102,13 @@ class CC_EXPORT HeadsUpDisplayLayerImpl : public LayerImpl { const SkRect& bounds) const; void DrawGraphLines(PaintCanvas* canvas, PaintFlags* flags, - const SkRect& bounds, - const Graph& graph) const; + const SkRect& bounds) const; - SkRect DrawFPSDisplay(PaintCanvas* canvas, - const FrameRateCounter* fps_counter, - int right, - int top) const; + SkRect DrawFrameThroughputDisplay( + PaintCanvas* canvas, + const DroppedFrameCounter* dropped_frame_counter, + int right, + int top) const; SkRect DrawMemoryDisplay(PaintCanvas* canvas, int top, int right, @@ -154,21 +136,15 @@ class CC_EXPORT HeadsUpDisplayLayerImpl : public LayerImpl { sk_sp<SkTypeface> typeface_; std::vector<gfx::Rect> layout_shift_rects_; - float internal_contents_scale_; + float internal_contents_scale_ = 1.0f; gfx::Size internal_content_bounds_; - Graph fps_graph_; - Graph paint_time_graph_; + uint32_t throughput_value_ = 0.0f; MemoryHistory::Entry memory_entry_; int paint_rects_fade_step_ = 0; int layout_shift_rects_fade_step_ = 0; std::vector<DebugRect> paint_rects_; std::vector<DebugRect> layout_shift_debug_rects_; - base::Optional<int> current_throughput_; - // The worst and best throughput we have seen so far, they either both have no - // value, or both have value. - base::Optional<int> min_throughput; - base::Optional<int> max_throughput; base::TimeTicks time_of_last_graph_update_; }; diff --git a/chromium/cc/layers/layer.cc b/chromium/cc/layers/layer.cc index c354081f17b..7b74ae2da3c 100644 --- a/chromium/cc/layers/layer.cc +++ b/chromium/cc/layers/layer.cc @@ -78,6 +78,7 @@ Layer::Inputs::Inputs(int layer_id) : layer_id(layer_id), hit_testable(false), contents_opaque(false), + contents_opaque_for_text(false), is_drawable(false), double_sided(true), has_will_change_transform_hint(false), @@ -804,11 +805,21 @@ void Layer::SetContentsOpaque(bool opaque) { if (inputs_.contents_opaque == opaque) return; inputs_.contents_opaque = opaque; + inputs_.contents_opaque_for_text = opaque; SetNeedsCommit(); SetSubtreePropertyChanged(); SetPropertyTreesNeedRebuild(); } +void Layer::SetContentsOpaqueForText(bool opaque) { + DCHECK(IsPropertyChangeAllowed()); + if (inputs_.contents_opaque_for_text == opaque) + return; + DCHECK(!contents_opaque() || opaque); + inputs_.contents_opaque_for_text = opaque; + SetNeedsCommit(); +} + void Layer::SetPosition(const gfx::PointF& position) { DCHECK(!layer_tree_host_ || !layer_tree_host_->IsUsingLayerLists()); @@ -1321,6 +1332,7 @@ void Layer::PushPropertiesTo(LayerImpl* layer) { layer->SetWheelEventHandlerRegion(Region()); } layer->SetContentsOpaque(inputs_.contents_opaque); + layer->SetContentsOpaqueForText(inputs_.contents_opaque_for_text); layer->SetShouldCheckBackfaceVisibility(should_check_backface_visibility_); layer->UpdateScrollable(); diff --git a/chromium/cc/layers/layer.h b/chromium/cc/layers/layer.h index 43debb199e8..a2da20587d1 100644 --- a/chromium/cc/layers/layer.h +++ b/chromium/cc/layers/layer.h @@ -333,9 +333,24 @@ class CC_EXPORT Layer : public base::RefCounted<Layer> { // must be opaque or visual errors can occur. This applies only to this layer // and not to children, and does not imply the layer should be composited // opaquely, as effects may be applied such as opacity() or filters(). + // Note that this also calls SetContentsOpaqueForText(opaque) internally. + // To override a different contents_opaque_for_text, the client should call + // SetContentsOpaqueForText() after SetContentsOpaque(). void SetContentsOpaque(bool opaque); bool contents_opaque() const { return inputs_.contents_opaque; } + // Whether the contents area containing text is known to be opaque. + // For example, blink will SetContentsOpaque(false) but + // SetContentsOpaqueForText(true) for the following case: + // <div style="overflow: hidden; border-radius: 10px; background: white"> + // TEXT + // </div> + // See also the note for SetContentsOpaque(). + void SetContentsOpaqueForText(bool opaque); + bool contents_opaque_for_text() const { + return inputs_.contents_opaque_for_text; + } + // Set or get whether this layer should be a hit test target void SetHitTestable(bool should_hit_test); virtual bool HitTestable() const; @@ -829,6 +844,7 @@ class CC_EXPORT Layer : public base::RefCounted<Layer> { bool hit_testable : 1; bool contents_opaque : 1; + bool contents_opaque_for_text : 1; bool is_drawable : 1; bool double_sided : 1; diff --git a/chromium/cc/layers/layer_impl.cc b/chromium/cc/layers/layer_impl.cc index bb1c93f2538..318c2cbc1ee 100644 --- a/chromium/cc/layers/layer_impl.cc +++ b/chromium/cc/layers/layer_impl.cc @@ -55,6 +55,7 @@ LayerImpl::LayerImpl(LayerTreeImpl* tree_impl, layer_property_changed_from_property_trees_(false), may_contain_video_(false), contents_opaque_(false), + contents_opaque_for_text_(false), should_check_backface_visibility_(false), draws_content_(false), contributes_to_drawn_render_surface_(false), @@ -374,6 +375,7 @@ void LayerImpl::PushPropertiesTo(LayerImpl* layer) { layer->has_transform_node_ = has_transform_node_; layer->offset_to_transform_parent_ = offset_to_transform_parent_; layer->contents_opaque_ = contents_opaque_; + layer->contents_opaque_for_text_ = contents_opaque_for_text_; layer->may_contain_video_ = may_contain_video_; layer->should_check_backface_visibility_ = should_check_backface_visibility_; layer->draws_content_ = draws_content_; @@ -571,6 +573,12 @@ SkColor LayerImpl::SafeOpaqueBackgroundColor() const { void LayerImpl::SetContentsOpaque(bool opaque) { contents_opaque_ = opaque; + contents_opaque_for_text_ = opaque; +} + +void LayerImpl::SetContentsOpaqueForText(bool opaque) { + DCHECK(!contents_opaque_ || opaque); + contents_opaque_for_text_ = opaque; } float LayerImpl::Opacity() const { diff --git a/chromium/cc/layers/layer_impl.h b/chromium/cc/layers/layer_impl.h index 95eb4828df8..006057b5b12 100644 --- a/chromium/cc/layers/layer_impl.h +++ b/chromium/cc/layers/layer_impl.h @@ -14,7 +14,7 @@ #include <string> #include <vector> -#include "base/logging.h" +#include "base/check.h" #include "base/memory/ptr_util.h" #include "cc/base/region.h" #include "cc/base/synced_property.h" @@ -165,8 +165,12 @@ class CC_EXPORT LayerImpl { // non-opaque color. Tries to return background_color(), if possible. SkColor SafeOpaqueBackgroundColor() const; + // See Layer::SetContentsOpaque() and SetContentsOpaqueForText() for the + // relationship between the two flags. void SetContentsOpaque(bool opaque); bool contents_opaque() const { return contents_opaque_; } + void SetContentsOpaqueForText(bool opaque); + bool contents_opaque_for_text() const { return contents_opaque_for_text_; } float Opacity() const; @@ -221,8 +225,8 @@ class CC_EXPORT LayerImpl { return draw_properties_.screen_space_transform_is_animating; } gfx::Rect clip_rect() const { return draw_properties_.clip_rect; } - gfx::Rect drawable_content_rect() const { - return draw_properties_.drawable_content_rect; + gfx::Rect visible_drawable_content_rect() const { + return draw_properties_.visible_drawable_content_rect; } gfx::Rect visible_layer_rect() const { return draw_properties_.visible_layer_rect; @@ -475,6 +479,7 @@ class CC_EXPORT LayerImpl { bool may_contain_video_ : 1; bool contents_opaque_ : 1; + bool contents_opaque_for_text_ : 1; bool should_check_backface_visibility_ : 1; bool draws_content_ : 1; bool contributes_to_drawn_render_surface_ : 1; diff --git a/chromium/cc/layers/layer_unittest.cc b/chromium/cc/layers/layer_unittest.cc index c49c99b106f..1d975284495 100644 --- a/chromium/cc/layers/layer_unittest.cc +++ b/chromium/cc/layers/layer_unittest.cc @@ -17,6 +17,7 @@ #include "cc/layers/picture_layer.h" #include "cc/layers/solid_color_scrollbar_layer.h" #include "cc/test/animation_test_common.h" +#include "cc/test/cc_test_suite.h" #include "cc/test/fake_content_layer_client.h" #include "cc/test/fake_impl_task_runner_provider.h" #include "cc/test/fake_layer_tree_host.h" @@ -1388,15 +1389,23 @@ TEST_F(LayerTest, DedupesCopyOutputRequestsBySource) { viz::CopyOutputRequest::ResultFormat::RGBA_BITMAP, base::BindOnce(&ReceiveCopyOutputResult, &result_count)); layer->RequestCopyOfOutput(std::move(request)); + // Because RequestCopyOfOutput could run as a PostTask to return results + // RunUntilIdle() to ensure that the result is not returned yet. + CCTestSuite::RunUntilIdle(); EXPECT_EQ(0, result_count); request = std::make_unique<viz::CopyOutputRequest>( viz::CopyOutputRequest::ResultFormat::RGBA_BITMAP, base::BindOnce(&ReceiveCopyOutputResult, &result_count)); layer->RequestCopyOfOutput(std::move(request)); + // Because RequestCopyOfOutput could run as a PostTask to return results + // RunUntilIdle() to ensure that the result is not returned yet. + CCTestSuite::RunUntilIdle(); EXPECT_EQ(0, result_count); // When the layer is destroyed, expect both requests to be aborted. layer = nullptr; + // Wait for any posted tasks to run so the results will be returned. + CCTestSuite::RunUntilIdle(); EXPECT_EQ(2, result_count); layer = Layer::Create(); @@ -1412,6 +1421,9 @@ TEST_F(LayerTest, DedupesCopyOutputRequestsBySource) { &did_receive_first_result_from_this_source)); request->set_source(kArbitrarySourceId1); layer->RequestCopyOfOutput(std::move(request)); + // Because RequestCopyOfOutput could run as a PostTask to return results + // RunUntilIdle() to ensure that the result is not returned yet. + CCTestSuite::RunUntilIdle(); EXPECT_EQ(0, did_receive_first_result_from_this_source); // Make a request from a different source. int did_receive_result_from_different_source = 0; @@ -1421,6 +1433,9 @@ TEST_F(LayerTest, DedupesCopyOutputRequestsBySource) { &did_receive_result_from_different_source)); request->set_source(kArbitrarySourceId2); layer->RequestCopyOfOutput(std::move(request)); + // Because RequestCopyOfOutput could run as a PostTask to return results + // RunUntilIdle() to ensure that the result is not returned yet. + CCTestSuite::RunUntilIdle(); EXPECT_EQ(0, did_receive_result_from_different_source); // Make a request without specifying the source. int did_receive_result_from_anonymous_source = 0; @@ -1429,6 +1444,9 @@ TEST_F(LayerTest, DedupesCopyOutputRequestsBySource) { base::BindOnce(&ReceiveCopyOutputResult, &did_receive_result_from_anonymous_source)); layer->RequestCopyOfOutput(std::move(request)); + // Because RequestCopyOfOutput could run as a PostTask to return results + // RunUntilIdle() to ensure that the result is not returned yet. + CCTestSuite::RunUntilIdle(); EXPECT_EQ(0, did_receive_result_from_anonymous_source); // Make the second request from |kArbitrarySourceId1|. int did_receive_second_result_from_this_source = 0; @@ -1439,6 +1457,8 @@ TEST_F(LayerTest, DedupesCopyOutputRequestsBySource) { request->set_source(kArbitrarySourceId1); layer->RequestCopyOfOutput( std::move(request)); // First request to be aborted. + // Wait for any posted tasks to run so the results will be returned. + CCTestSuite::RunUntilIdle(); EXPECT_EQ(1, did_receive_first_result_from_this_source); EXPECT_EQ(0, did_receive_result_from_different_source); EXPECT_EQ(0, did_receive_result_from_anonymous_source); @@ -1446,6 +1466,8 @@ TEST_F(LayerTest, DedupesCopyOutputRequestsBySource) { // When the layer is destroyed, the other three requests should be aborted. layer = nullptr; + // Wait for any posted tasks to run so the results will be returned. + CCTestSuite::RunUntilIdle(); EXPECT_EQ(1, did_receive_first_result_from_this_source); EXPECT_EQ(1, did_receive_result_from_different_source); EXPECT_EQ(1, did_receive_result_from_anonymous_source); @@ -1507,7 +1529,7 @@ TEST_F(LayerTest, SetLayerTreeHostNotUsingLayerListsManagesElementId) { // Expect additional calls due to has-animation check and initialization // of keyframes. - EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(7); + EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(3); scoped_refptr<AnimationTimeline> timeline = AnimationTimeline::Create(AnimationIdProvider::NextTimelineId()); animation_host_->AddAnimationTimeline(timeline); @@ -1526,6 +1548,19 @@ TEST_F(LayerTest, SetLayerTreeHostNotUsingLayerListsManagesElementId) { EXPECT_EQ(nullptr, layer_tree_host_->LayerByElementId(element_id)); } +// Triggering a commit to push animation counts and raf presence to the +// compositor is expensive and updated counts can wait until the next +// commit to be pushed. See https://crbug.com/1083244. +TEST_F(LayerTest, PushAnimationCountsLazily) { + EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(0); + animation_host_->SetAnimationCounts(0, /* current_frame_had_raf = */ true, + /* next_frame_has_pending_raf = */ true); + EXPECT_FALSE(host_impl_.animation_host()->CurrentFrameHadRAF()); + EXPECT_FALSE(animation_host_->needs_push_properties()); + animation_host_->PushPropertiesTo(host_impl_.animation_host()); + EXPECT_TRUE(host_impl_.animation_host()->CurrentFrameHadRAF()); +} + TEST_F(LayerTest, SetElementIdNotUsingLayerLists) { scoped_refptr<Layer> test_layer = Layer::Create(); test_layer->SetLayerTreeHost(layer_tree_host_.get()); diff --git a/chromium/cc/layers/mirror_layer_impl.cc b/chromium/cc/layers/mirror_layer_impl.cc index 7a916bf5d69..5751ccb53b2 100644 --- a/chromium/cc/layers/mirror_layer_impl.cc +++ b/chromium/cc/layers/mirror_layer_impl.cc @@ -56,8 +56,7 @@ void MirrorLayerImpl::AppendQuads(viz::RenderPass* render_pass, quad->SetNew(shared_quad_state, content_rect, unoccluded_content_rect, mirrored_layer_id_, mask_resource_id, mask_uv_rect, mask_texture_size, mirrored_effect_node->surface_contents_scale, - mirrored_effect_node->filters_origin, - gfx::RectF(gfx::Rect(content_rect.size())), + gfx::PointF(), gfx::RectF(gfx::Rect(content_rect.size())), !layer_tree_impl()->settings().enable_edge_anti_aliasing, 0.f); } diff --git a/chromium/cc/layers/painted_scrollbar_layer.cc b/chromium/cc/layers/painted_scrollbar_layer.cc index 84f6b872302..2be635c50e2 100644 --- a/chromium/cc/layers/painted_scrollbar_layer.cc +++ b/chromium/cc/layers/painted_scrollbar_layer.cc @@ -40,6 +40,7 @@ PaintedScrollbarLayer::PaintedScrollbarLayer(scoped_refptr<Scrollbar> scrollbar) internal_contents_scale_(1.f), painted_opacity_(scrollbar_->Opacity()), has_thumb_(scrollbar_->HasThumb()), + jump_on_track_click_(scrollbar_->JumpOnTrackClick()), supports_drag_snap_back_(scrollbar_->SupportsDragSnapBack()), is_overlay_(scrollbar_->IsOverlay()) {} @@ -58,6 +59,7 @@ void PaintedScrollbarLayer::PushPropertiesTo(LayerImpl* layer) { scrollbar_layer->set_internal_contents_scale_and_bounds( internal_contents_scale_, internal_content_bounds_); + scrollbar_layer->SetJumpOnTrackClick(jump_on_track_click_); scrollbar_layer->SetSupportsDragSnapBack(supports_drag_snap_back_); scrollbar_layer->SetBackButtonRect(back_button_rect_); scrollbar_layer->SetForwardButtonRect(forward_button_rect_); @@ -112,6 +114,7 @@ void PaintedScrollbarLayer::UpdateThumbAndTrackGeometry() { DCHECK_EQ(is_overlay_, scrollbar_->IsOverlay()); DCHECK_EQ(orientation(), scrollbar_->Orientation()); + UpdateProperty(scrollbar_->JumpOnTrackClick(), &jump_on_track_click_); UpdateProperty(scrollbar_->TrackRect(), &track_rect_); UpdateProperty(scrollbar_->BackButtonRect(), &back_button_rect_); UpdateProperty(scrollbar_->ForwardButtonRect(), &forward_button_rect_); diff --git a/chromium/cc/layers/painted_scrollbar_layer.h b/chromium/cc/layers/painted_scrollbar_layer.h index 191c37d8e5a..930c614071e 100644 --- a/chromium/cc/layers/painted_scrollbar_layer.h +++ b/chromium/cc/layers/painted_scrollbar_layer.h @@ -86,6 +86,7 @@ class CC_EXPORT PaintedScrollbarLayer : public ScrollbarLayerBase { gfx::Rect forward_button_rect_; float painted_opacity_; bool has_thumb_; + bool jump_on_track_click_; const bool supports_drag_snap_back_; const bool is_overlay_; diff --git a/chromium/cc/layers/painted_scrollbar_layer_impl.cc b/chromium/cc/layers/painted_scrollbar_layer_impl.cc index 70da5b1be8f..6d36154b803 100644 --- a/chromium/cc/layers/painted_scrollbar_layer_impl.cc +++ b/chromium/cc/layers/painted_scrollbar_layer_impl.cc @@ -43,6 +43,7 @@ PaintedScrollbarLayerImpl::PaintedScrollbarLayerImpl( thumb_ui_resource_id_(0), painted_opacity_(1.f), internal_contents_scale_(1.f), + jump_on_track_click_(false), supports_drag_snap_back_(false), thumb_thickness_(0), thumb_length_(0) {} @@ -65,6 +66,7 @@ void PaintedScrollbarLayerImpl::PushPropertiesTo(LayerImpl* layer) { scrollbar_layer->set_internal_contents_scale_and_bounds( internal_contents_scale_, internal_content_bounds_); + scrollbar_layer->SetJumpOnTrackClick(jump_on_track_click_); scrollbar_layer->SetSupportsDragSnapBack(supports_drag_snap_back_); scrollbar_layer->SetThumbThickness(thumb_thickness_); scrollbar_layer->SetThumbLength(thumb_length_); @@ -162,6 +164,17 @@ gfx::Rect PaintedScrollbarLayerImpl::GetEnclosingRectInTargetSpace() const { return GetScaledEnclosingRectInTargetSpace(internal_contents_scale_); } +void PaintedScrollbarLayerImpl::SetJumpOnTrackClick(bool jump_on_track_click) { + if (jump_on_track_click_ == jump_on_track_click) + return; + jump_on_track_click_ = jump_on_track_click; + NoteLayerPropertyChanged(); +} + +bool PaintedScrollbarLayerImpl::JumpOnTrackClick() const { + return jump_on_track_click_; +} + void PaintedScrollbarLayerImpl::SetSupportsDragSnapBack( bool supports_drag_snap_back) { if (supports_drag_snap_back_ == supports_drag_snap_back) diff --git a/chromium/cc/layers/painted_scrollbar_layer_impl.h b/chromium/cc/layers/painted_scrollbar_layer_impl.h index e5035e1dc3b..eecf7c9aefd 100644 --- a/chromium/cc/layers/painted_scrollbar_layer_impl.h +++ b/chromium/cc/layers/painted_scrollbar_layer_impl.h @@ -38,6 +38,7 @@ class CC_EXPORT PaintedScrollbarLayerImpl : public ScrollbarLayerImplBase { AppendQuadsData* append_quads_data) override; gfx::Rect GetEnclosingRectInTargetSpace() const override; + void SetJumpOnTrackClick(bool jump_on_track_click); void SetSupportsDragSnapBack(bool supports_drag_snap_back); void SetBackButtonRect(gfx::Rect back_button_rect); void SetForwardButtonRect(gfx::Rect forward_button_rect); @@ -63,6 +64,7 @@ class CC_EXPORT PaintedScrollbarLayerImpl : public ScrollbarLayerImplBase { internal_content_bounds_ = content_bounds; } + bool JumpOnTrackClick() const override; bool SupportsDragSnapBack() const override; gfx::Rect BackButtonRect() const override; gfx::Rect ForwardButtonRect() const override; @@ -98,6 +100,7 @@ class CC_EXPORT PaintedScrollbarLayerImpl : public ScrollbarLayerImplBase { float internal_contents_scale_; gfx::Size internal_content_bounds_; + bool jump_on_track_click_; bool supports_drag_snap_back_; int thumb_thickness_; int thumb_length_; diff --git a/chromium/cc/layers/picture_image_layer.cc b/chromium/cc/layers/picture_image_layer.cc deleted file mode 100644 index 2208b11c6a1..00000000000 --- a/chromium/cc/layers/picture_image_layer.cc +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2010 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/layers/picture_image_layer.h" - -#include <stddef.h> - -#include "cc/base/math_util.h" -#include "cc/layers/picture_layer_impl.h" -#include "cc/paint/paint_op_buffer.h" -#include "cc/trees/layer_tree_host.h" -#include "cc/trees/layer_tree_settings.h" - -namespace cc { - -scoped_refptr<PictureImageLayer> PictureImageLayer::Create() { - return base::WrapRefCounted(new PictureImageLayer()); -} - -PictureImageLayer::PictureImageLayer() : PictureLayer(this) {} - -PictureImageLayer::~PictureImageLayer() { - ClearClient(); -} - -bool PictureImageLayer::HasDrawableContent() const { - return image_ && PictureLayer::HasDrawableContent(); -} - -void PictureImageLayer::SetImage(PaintImage image, - const SkMatrix& matrix, - bool uses_width_as_height) { - // SetImage() currently gets called whenever there is any - // style change that affects the layer even if that change doesn't - // affect the actual contents of the image (e.g. a CSS animation). - // With this check in place we avoid unecessary texture uploads. - if (image_ == image && matrix_ == matrix && - uses_width_as_height_ == uses_width_as_height) { - return; - } - - image_ = std::move(image); - matrix_ = matrix; - uses_width_as_height_ = uses_width_as_height; - - int width = uses_width_as_height_ ? image_.height() : image_.width(); - int height = uses_width_as_height_ ? image_.width() : image_.height(); - picture_layer_inputs_.directly_composited_image_size = - gfx::Size(width, height); - - UpdateDrawsContent(HasDrawableContent()); - SetNeedsDisplay(); -} - -gfx::Rect PictureImageLayer::PaintableRegion() { - return gfx::Rect(bounds()); -} - -scoped_refptr<DisplayItemList> PictureImageLayer::PaintContentsToDisplayList( - ContentLayerClient::PaintingControlSetting painting_control) { - DCHECK(image_); - DCHECK_GT(image_.width(), 0); - DCHECK_GT(image_.height(), 0); - DCHECK(layer_tree_host()); - - gfx::Size image_size = - picture_layer_inputs_.directly_composited_image_size.value(); - float content_to_layer_scale_x = - static_cast<float>(bounds().width()) / image_size.width(); - float content_to_layer_scale_y = - static_cast<float>(bounds().height()) / image_size.height(); - - bool has_scale = !MathUtil::IsWithinEpsilon(content_to_layer_scale_x, 1.f) || - !MathUtil::IsWithinEpsilon(content_to_layer_scale_y, 1.f); - DCHECK(!has_scale); - bool needs_save = has_scale || !matrix_.isIdentity(); - - auto display_list = base::MakeRefCounted<DisplayItemList>(); - - display_list->StartPaint(); - - if (needs_save) - display_list->push<SaveOp>(); - - if (has_scale) { - display_list->push<ScaleOp>(content_to_layer_scale_x, - content_to_layer_scale_y); - } - - if (!matrix_.isIdentity()) - display_list->push<ConcatOp>(matrix_); - - // Because Android WebView resourceless software draw mode rasters directly - // to the root canvas, this draw must use the SkBlendMode::kSrcOver so that - // transparent images blend correctly. - display_list->push<DrawImageOp>(image_, 0.f, 0.f, nullptr); - - if (needs_save) - display_list->push<RestoreOp>(); - - display_list->EndPaintOfUnpaired(PaintableRegion()); - display_list->Finalize(); - - return display_list; -} - -bool PictureImageLayer::FillsBoundsCompletely() const { - return false; -} - -size_t PictureImageLayer::GetApproximateUnsharedMemoryUsage() const { - return 0; -} - -} // namespace cc diff --git a/chromium/cc/layers/picture_image_layer.h b/chromium/cc/layers/picture_image_layer.h deleted file mode 100644 index 92fddfad360..00000000000 --- a/chromium/cc/layers/picture_image_layer.h +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2010 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_LAYERS_PICTURE_IMAGE_LAYER_H_ -#define CC_LAYERS_PICTURE_IMAGE_LAYER_H_ - -#include <stddef.h> - -#include "cc/cc_export.h" -#include "cc/layers/content_layer_client.h" -#include "cc/layers/picture_layer.h" -#include "cc/paint/paint_image.h" -#include "third_party/skia/include/core/SkRefCnt.h" -#include "ui/gfx/geometry/size.h" - -namespace cc { - -class CC_EXPORT PictureImageLayer : public PictureLayer, ContentLayerClient { - public: - static scoped_refptr<PictureImageLayer> Create(); - - PictureImageLayer(const PictureImageLayer&) = delete; - PictureImageLayer& operator=(const PictureImageLayer&) = delete; - - void SetImage(PaintImage image, - const SkMatrix& matrix, - bool uses_width_as_height); - - gfx::Rect PaintableRegion() override; - - // ContentLayerClient implementation. - scoped_refptr<DisplayItemList> PaintContentsToDisplayList( - ContentLayerClient::PaintingControlSetting painting_control) override; - bool FillsBoundsCompletely() const override; - size_t GetApproximateUnsharedMemoryUsage() const override; - - protected: - bool HasDrawableContent() const override; - - private: - PictureImageLayer(); - ~PictureImageLayer() override; - - PaintImage image_; - SkMatrix matrix_; - bool uses_width_as_height_; -}; - -} // namespace cc - -#endif // CC_LAYERS_PICTURE_IMAGE_LAYER_H_ diff --git a/chromium/cc/layers/picture_image_layer_unittest.cc b/chromium/cc/layers/picture_image_layer_unittest.cc deleted file mode 100644 index f02c127aa37..00000000000 --- a/chromium/cc/layers/picture_image_layer_unittest.cc +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2015 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/layers/picture_image_layer.h" - -#include "cc/animation/animation_host.h" -#include "cc/paint/paint_image_builder.h" -#include "cc/test/fake_layer_tree_host.h" -#include "cc/test/skia_common.h" -#include "cc/test/test_task_graph_runner.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "third_party/skia/include/core/SkCanvas.h" -#include "third_party/skia/include/core/SkColor.h" -#include "third_party/skia/include/core/SkImage.h" -#include "third_party/skia/include/core/SkSurface.h" - -namespace cc { -namespace { - -TEST(PictureImageLayerTest, PaintContentsToDisplayList) { - scoped_refptr<PictureImageLayer> layer = PictureImageLayer::Create(); - FakeLayerTreeHostClient client; - TestTaskGraphRunner task_graph_runner; - auto animation_host = AnimationHost::CreateForTesting(ThreadInstance::MAIN); - std::unique_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create( - &client, &task_graph_runner, animation_host.get()); - layer->SetLayerTreeHost(host.get()); - gfx::Rect layer_rect(200, 200); - - unsigned char image_pixels[4 * 200 * 200] = {0}; - SkImageInfo info = - SkImageInfo::MakeN32Premul(layer_rect.width(), layer_rect.height()); - sk_sp<SkSurface> image_surface = - SkSurface::MakeRasterDirect(info, image_pixels, info.minRowBytes()); - SkCanvas* image_canvas = image_surface->getCanvas(); - image_canvas->clear(SK_ColorRED); - SkPaint blue_paint; - blue_paint.setColor(SK_ColorBLUE); - image_canvas->drawRect(SkRect::MakeWH(100, 100), blue_paint); - image_canvas->drawRect(SkRect::MakeLTRB(100, 100, 200, 200), blue_paint); - - layer->SetImage(PaintImageBuilder::WithDefault() - .set_id(PaintImage::GetNextId()) - .set_image(image_surface->makeImageSnapshot(), - PaintImage::GetNextContentId()) - .TakePaintImage(), - SkMatrix::I(), false); - layer->SetBounds(gfx::Size(layer_rect.width(), layer_rect.height())); - - scoped_refptr<DisplayItemList> display_list = - layer->PaintContentsToDisplayList( - ContentLayerClient::PAINTING_BEHAVIOR_NORMAL); - unsigned char actual_pixels[4 * 200 * 200] = {0}; - DrawDisplayList(actual_pixels, layer_rect, display_list); - - EXPECT_EQ(0, memcmp(actual_pixels, image_pixels, 4 * 200 * 200)); - - layer->SetLayerTreeHost(nullptr); -} - -} // namespace -} // namespace cc diff --git a/chromium/cc/layers/picture_layer.cc b/chromium/cc/layers/picture_layer.cc index b7244152c31..0baee75a961 100644 --- a/chromium/cc/layers/picture_layer.cc +++ b/chromium/cc/layers/picture_layer.cc @@ -137,9 +137,17 @@ bool PictureLayer::Update() { &last_updated_invalidation_, layer_size, recorded_viewport); if (updated) { - picture_layer_inputs_.display_list = - picture_layer_inputs_.client->PaintContentsToDisplayList( - ContentLayerClient::PAINTING_BEHAVIOR_NORMAL); + { + auto old_display_list = std::move(picture_layer_inputs_.display_list); + picture_layer_inputs_.display_list = + picture_layer_inputs_.client->PaintContentsToDisplayList( + ContentLayerClient::PAINTING_BEHAVIOR_NORMAL); + if (old_display_list && + picture_layer_inputs_.display_list + ->NeedsAdditionalInvalidationForLCDText(*old_display_list)) { + last_updated_invalidation_ = gfx::Rect(bounds()); + } + } // Clear out previous directly composited image state - if the layer // qualifies we'll set up the state below. @@ -282,14 +290,6 @@ bool PictureLayer::ShouldUseTransformedRasterization() const { if (!picture_layer_inputs_.transformed_rasterization_allowed) return false; - // Background color overfill is undesirable with transformed rasterization. - // However, without background overfill, the tiles will be non-opaque on - // external edges, and layer opaque region can't be computed in layer space - // due to rounding under extreme scaling. This defeats many opaque layer - // optimization. Prefer optimization over quality for this particular case. - if (contents_opaque()) - return false; - const TransformTree& transform_tree = layer_tree_host()->property_trees()->transform_tree; DCHECK(!transform_tree.needs_update()); diff --git a/chromium/cc/layers/picture_layer_impl.cc b/chromium/cc/layers/picture_layer_impl.cc index 00e845e2b3f..0f030bd4ff3 100644 --- a/chromium/cc/layers/picture_layer_impl.cc +++ b/chromium/cc/layers/picture_layer_impl.cc @@ -258,12 +258,16 @@ void PictureLayerImpl::AppendQuads(viz::RenderPass* render_pass, // |PictureLayerTiling::EnclosingContentsRectFromLayer()| will // create a tiling that, when scaled by |max_contents_scale| above, is // larger than the layer bounds by a fraction of a pixel. - gfx::Rect clip_rect = draw_properties().drawable_content_rect; + gfx::Rect bounds_in_target_space = MathUtil::MapEnclosingClippedRect( + draw_properties().target_space_transform, gfx::Rect(bounds())); + if (is_clipped()) + bounds_in_target_space.Intersect(draw_properties().clip_rect); + if (shared_quad_state->is_clipped) - clip_rect.Intersect(shared_quad_state->clip_rect); + bounds_in_target_space.Intersect(shared_quad_state->clip_rect); shared_quad_state->is_clipped = true; - shared_quad_state->clip_rect = clip_rect; + shared_quad_state->clip_rect = bounds_in_target_space; #if DCHECK_IS_ON() // Validate that the tile and bounds size are always within one pixel. @@ -836,22 +840,24 @@ LCDTextDisallowedReason PictureLayerImpl::ComputeLCDTextDisallowedReason() return LCDTextDisallowedReason::kNone; if (!layer_tree_impl()->settings().can_use_lcd_text) return LCDTextDisallowedReason::kSetting; - if (!contents_opaque()) { + if (!contents_opaque_for_text()) { if (SkColorGetA(background_color()) != SK_AlphaOPAQUE) return LCDTextDisallowedReason::kBackgroundColorNotOpaque; return LCDTextDisallowedReason::kContentsNotOpaque; } - if (!GetTransformTree() - .Node(transform_tree_index()) - ->node_and_ancestors_have_only_integer_translation) - return LCDTextDisallowedReason::kNonIntegralTranslation; - if (static_cast<int>(offset_to_transform_parent().x()) != - offset_to_transform_parent().x()) - return LCDTextDisallowedReason::kNonIntegralXOffset; - if (static_cast<int>(offset_to_transform_parent().y()) != - offset_to_transform_parent().y()) - return LCDTextDisallowedReason::kNonIntegralYOffset; + if (!use_transformed_rasterization_) { + if (!GetTransformTree() + .Node(transform_tree_index()) + ->node_and_ancestors_have_only_integer_translation) + return LCDTextDisallowedReason::kNonIntegralTranslation; + if (static_cast<int>(offset_to_transform_parent().x()) != + offset_to_transform_parent().x()) + return LCDTextDisallowedReason::kNonIntegralXOffset; + if (static_cast<int>(offset_to_transform_parent().y()) != + offset_to_transform_parent().y()) + return LCDTextDisallowedReason::kNonIntegralYOffset; + } if (has_will_change_transform_hint()) return LCDTextDisallowedReason::kWillChangeTransform; @@ -1066,6 +1072,11 @@ void PictureLayerImpl::SetNearestNeighbor(bool nearest_neighbor) { } void PictureLayerImpl::SetUseTransformedRasterization(bool use) { + // With transformed rasterization, the pixels along the edge of the layer may + // become translucent, so clear contents_opaque. + if (use) + SetContentsOpaque(false); + if (use_transformed_rasterization_ == use) return; @@ -1561,6 +1572,8 @@ gfx::Vector2dF PictureLayerImpl::CalculateRasterTranslation( if (!use_transformed_rasterization_) return gfx::Vector2dF(); + DCHECK(!contents_opaque()); + gfx::Transform draw_transform = DrawTransform(); // TODO(enne): for performance reasons, we should only have a raster // translation when the screen space transform is not animating. We try to diff --git a/chromium/cc/layers/picture_layer_impl_unittest.cc b/chromium/cc/layers/picture_layer_impl_unittest.cc index 25fff7cb2ae..38609980105 100644 --- a/chromium/cc/layers/picture_layer_impl_unittest.cc +++ b/chromium/cc/layers/picture_layer_impl_unittest.cc @@ -5981,5 +5981,33 @@ TEST_F(LegacySWPictureLayerImplTest, NoTilingsUsesScaleOne) { EXPECT_RECT_EQ(gfx::Rect(1000, 10000), shared_quad_state->quad_layer_rect); EXPECT_TRUE(shared_quad_state->quad_to_target_transform.IsIdentity()); } + +TEST_F(LegacySWPictureLayerImplTest, + TransformedRasterizationAndContentsOpaqueAndLCDText) { + SetupDefaultTreesWithInvalidation(gfx::Size(200, 200), Region()); + + pending_layer()->SetBackgroundColor(SK_ColorWHITE); + pending_layer()->SetContentsOpaque(true); + pending_layer()->SetOffsetToTransformParent(gfx::Vector2dF(0.2, 0.3)); + EXPECT_TRUE(pending_layer()->contents_opaque()); + EXPECT_TRUE(pending_layer()->contents_opaque_for_text()); + EXPECT_EQ(LCDTextDisallowedReason::kNonIntegralXOffset, + pending_layer()->ComputeLCDTextDisallowedReasonForTesting()); + + pending_layer()->SetUseTransformedRasterization(true); + EXPECT_FALSE(pending_layer()->contents_opaque()); + EXPECT_FALSE(pending_layer()->contents_opaque_for_text()); + EXPECT_EQ(LCDTextDisallowedReason::kContentsNotOpaque, + pending_layer()->ComputeLCDTextDisallowedReasonForTesting()); + + // Simulate another push from main-thread with the same values. + pending_layer()->SetContentsOpaque(true); + pending_layer()->SetUseTransformedRasterization(true); + EXPECT_FALSE(pending_layer()->contents_opaque()); + EXPECT_FALSE(pending_layer()->contents_opaque_for_text()); + EXPECT_EQ(LCDTextDisallowedReason::kContentsNotOpaque, + pending_layer()->ComputeLCDTextDisallowedReasonForTesting()); +} + } // namespace } // namespace cc diff --git a/chromium/cc/layers/render_surface_impl.cc b/chromium/cc/layers/render_surface_impl.cc index 3d60067a835..f3cd766fdd5 100644 --- a/chromium/cc/layers/render_surface_impl.cc +++ b/chromium/cc/layers/render_surface_impl.cc @@ -134,10 +134,6 @@ const FilterOperations& RenderSurfaceImpl::Filters() const { return OwningEffectNode()->filters; } -gfx::PointF RenderSurfaceImpl::FiltersOrigin() const { - return OwningEffectNode()->filters_origin; -} - gfx::Transform RenderSurfaceImpl::SurfaceScale() const { gfx::Transform surface_scale; surface_scale.Scale(OwningEffectNode()->surface_contents_scale.x(), @@ -303,7 +299,7 @@ void RenderSurfaceImpl::AccumulateContentRectFromContributingLayer( if (render_target() == this) return; - accumulated_content_rect_.Union(layer->drawable_content_rect()); + accumulated_content_rect_.Union(layer->visible_drawable_content_rect()); } void RenderSurfaceImpl::AccumulateContentRectFromContributingRenderSurface( @@ -456,7 +452,7 @@ void RenderSurfaceImpl::AppendQuads(DrawMode draw_mode, auto* quad = render_pass->CreateAndAppendDrawQuad<viz::RenderPassDrawQuad>(); quad->SetAll(shared_quad_state, content_rect(), unoccluded_content_rect, /*needs_blending=*/true, id(), mask_resource_id, mask_uv_rect, - mask_texture_size, surface_contents_scale, FiltersOrigin(), + mask_texture_size, surface_contents_scale, gfx::PointF(), tex_coord_rect, !layer_tree_impl_->settings().enable_edge_anti_aliasing, OwningEffectNode()->backdrop_filter_quality, diff --git a/chromium/cc/layers/render_surface_impl.h b/chromium/cc/layers/render_surface_impl.h index 72587093ba7..fac0274a79d 100644 --- a/chromium/cc/layers/render_surface_impl.h +++ b/chromium/cc/layers/render_surface_impl.h @@ -168,7 +168,6 @@ class CC_EXPORT RenderSurfaceImpl { const FilterOperations& BackdropFilters() const; base::Optional<gfx::RRectF> BackdropFilterBounds() const; LayerImpl* BackdropMaskLayer() const; - gfx::PointF FiltersOrigin() const; gfx::Transform SurfaceScale() const; bool TrilinearFiltering() const; diff --git a/chromium/cc/layers/scrollbar_layer_impl_base.cc b/chromium/cc/layers/scrollbar_layer_impl_base.cc index 2d9e885c030..787f339fb49 100644 --- a/chromium/cc/layers/scrollbar_layer_impl_base.cc +++ b/chromium/cc/layers/scrollbar_layer_impl_base.cc @@ -291,6 +291,10 @@ bool ScrollbarLayerImplBase::SupportsDragSnapBack() const { return false; } +bool ScrollbarLayerImplBase::JumpOnTrackClick() const { + return false; +} + gfx::Rect ScrollbarLayerImplBase::BackButtonRect() const { return gfx::Rect(0, 0); } diff --git a/chromium/cc/layers/scrollbar_layer_impl_base.h b/chromium/cc/layers/scrollbar_layer_impl_base.h index 3b508f3c17a..a887ab47ed2 100644 --- a/chromium/cc/layers/scrollbar_layer_impl_base.h +++ b/chromium/cc/layers/scrollbar_layer_impl_base.h @@ -72,6 +72,7 @@ class CC_EXPORT ScrollbarLayerImplBase : public LayerImpl { virtual gfx::Rect BackTrackRect() const; virtual gfx::Rect ForwardTrackRect() const; virtual bool SupportsDragSnapBack() const; + virtual bool JumpOnTrackClick() const; virtual ScrollbarPart IdentifyScrollbarPart( const gfx::PointF position_in_widget) const; // Only PaintedOverlayScrollbar(Aura Overlay Scrollbar) need to know diff --git a/chromium/cc/layers/solid_color_layer_impl.cc b/chromium/cc/layers/solid_color_layer_impl.cc index 48c728cfde6..242f76415f9 100644 --- a/chromium/cc/layers/solid_color_layer_impl.cc +++ b/chromium/cc/layers/solid_color_layer_impl.cc @@ -53,6 +53,9 @@ void SolidColorLayerImpl::AppendSolidQuads( gfx::Rect visible_quad_rect = occlusion_in_layer_space.GetUnoccludedContentRect(visible_layer_rect); + if (visible_quad_rect.IsEmpty()) + return; + auto* quad = render_pass->CreateAndAppendDrawQuad<viz::SolidColorDrawQuad>(); quad->SetNew(shared_quad_state, visible_layer_rect, visible_quad_rect, color, force_anti_aliasing_off); diff --git a/chromium/cc/layers/texture_layer_impl.cc b/chromium/cc/layers/texture_layer_impl.cc index 5b4229280eb..544676a563b 100644 --- a/chromium/cc/layers/texture_layer_impl.cc +++ b/chromium/cc/layers/texture_layer_impl.cc @@ -9,6 +9,7 @@ #include <vector> +#include "base/logging.h" #include "base/strings/stringprintf.h" #include "cc/trees/layer_tree_frame_sink.h" #include "cc/trees/layer_tree_impl.h" diff --git a/chromium/cc/layers/video_layer_impl_unittest.cc b/chromium/cc/layers/video_layer_impl_unittest.cc index 8a22f392e56..06197477989 100644 --- a/chromium/cc/layers/video_layer_impl_unittest.cc +++ b/chromium/cc/layers/video_layer_impl_unittest.cc @@ -395,8 +395,7 @@ TEST(VideoLayerImplTest, NativeYUVFrameGeneratesYUVQuad) { gfx::Size(10, 10), gfx::Rect(10, 10), gfx::Size(10, 10), base::TimeDelta()); ASSERT_TRUE(video_frame); - video_frame->metadata()->SetBoolean(media::VideoFrameMetadata::ALLOW_OVERLAY, - true); + video_frame->metadata()->allow_overlay = true; FakeVideoFrameProvider provider; provider.set_frame(video_frame); @@ -439,8 +438,7 @@ TEST(VideoLayerImplTest, NativeARGBFrameGeneratesTextureQuad) { media::PIXEL_FORMAT_ARGB, mailbox_holders, base::DoNothing(), resource_size, gfx::Rect(10, 10), resource_size, base::TimeDelta()); ASSERT_TRUE(video_frame); - video_frame->metadata()->SetBoolean(media::VideoFrameMetadata::ALLOW_OVERLAY, - true); + video_frame->metadata()->allow_overlay = true; FakeVideoFrameProvider provider; provider.set_frame(video_frame); diff --git a/chromium/cc/metrics/compositor_frame_reporter.cc b/chromium/cc/metrics/compositor_frame_reporter.cc index 13c15d9b1dd..c5b037edf4d 100644 --- a/chromium/cc/metrics/compositor_frame_reporter.cc +++ b/chromium/cc/metrics/compositor_frame_reporter.cc @@ -4,6 +4,7 @@ #include "cc/metrics/compositor_frame_reporter.h" +#include <algorithm> #include <memory> #include <string> #include <utility> @@ -11,10 +12,14 @@ #include "base/metrics/histogram_macros.h" #include "base/stl_util.h" #include "base/strings/strcat.h" +#include "base/time/time.h" #include "base/trace_event/trace_event.h" #include "cc/base/rolling_time_delta_history.h" +#include "cc/metrics/dropped_frame_counter.h" #include "cc/metrics/frame_sequence_tracker.h" #include "cc/metrics/latency_ukm_reporter.h" +#include "services/tracing/public/cpp/perfetto/macros.h" +#include "third_party/perfetto/protos/perfetto/trace/track_event/chrome_frame_reporter.pbzero.h" #include "ui/events/types/event_type.h" namespace cc { @@ -42,7 +47,8 @@ constexpr int kFrameSequenceTrackerTypeCount = static_cast<int>(FrameSequenceTrackerType::kMaxType) + 1; // Names for the viz breakdowns that are shown in trace as substages under -// PipelineReporter -> SubmitCompositorFrameToPresentationCompositorFrame +// PipelineReporter -> SubmitCompositorFrameToPresentationCompositorFrame or +// EventLatency -> SubmitCompositorFrameToPresentationCompositorFrame. constexpr const char* GetVizBreakdownName(VizBreakdown stage) { switch (stage) { case VizBreakdown::kSubmitToReceiveCompositorFrame: @@ -55,6 +61,14 @@ constexpr const char* GetVizBreakdownName(VizBreakdown stage) { return "Swap"; case VizBreakdown::kSwapEndToPresentationCompositorFrame: return "SwapEndToPresentationCompositorFrame"; + case VizBreakdown::kSwapStartToBufferAvailable: + return "SwapStartToBufferAvailable"; + case VizBreakdown::kBufferAvailableToBufferReady: + return "BufferAvailableToBufferReady"; + case VizBreakdown::kBufferReadyToLatch: + return "BufferReadyToLatch"; + case VizBreakdown::kLatchToSwapEnd: + return "LatchToSwapEnd"; case VizBreakdown::kBreakdownCount: NOTREACHED(); return ""; @@ -165,9 +179,9 @@ constexpr int kCompositorLatencyHistogramMax = 350000; constexpr int kCompositorLatencyHistogramBucketCount = 50; constexpr int kEventLatencyEventTypeCount = - static_cast<int>(ui::EventType::ET_LAST); + static_cast<int>(EventMetrics::EventType::kMaxValue) + 1; constexpr int kEventLatencyScrollTypeCount = - static_cast<int>(ui::ScrollInputType::kMaxValue) + 1; + static_cast<int>(EventMetrics::ScrollType::kMaxValue) + 1; constexpr int kMaxEventLatencyHistogramBaseIndex = kEventLatencyEventTypeCount * kEventLatencyScrollTypeCount; constexpr int kMaxEventLatencyHistogramIndex = @@ -203,37 +217,81 @@ std::string GetCompositorLatencyHistogramName( std::string GetEventLatencyHistogramBaseName( const EventMetrics& event_metrics) { - const bool is_scroll = event_metrics.scroll_input_type().has_value(); + const bool is_scroll = event_metrics.scroll_type().has_value(); return base::StrCat( {"EventLatency.", event_metrics.GetTypeName(), is_scroll ? "." : nullptr, is_scroll ? event_metrics.GetScrollTypeName() : nullptr}); } +base::TimeTicks ComputeSafeDeadlineForFrame(const viz::BeginFrameArgs& args) { + return args.frame_time + (args.interval * 1.5); +} + +void ReportOffsetBetweenDeadlineAndPresentationTime( + const viz::BeginFrameArgs& args, + base::TimeTicks presentation_time) { + const base::TimeTicks strict_deadline = args.frame_time + args.interval; + const base::TimeDelta offset = presentation_time - strict_deadline; + // |strict_deadline| and |presentation_time| should normally be pretty close. + // Measure how close they are. + UMA_HISTOGRAM_CUSTOM_TIMES( + "CompositorLatency.Diagnostic.PresentationTimeDeltaFromDeadline", offset, + base::TimeDelta::FromMilliseconds(1), + base::TimeDelta::FromMilliseconds(32), /*bucket_count=*/16); +} + +#define REPORT_VIZ_TRACE_EVENT(start_time, end_time, index, trace_func) \ + if (start_time <= end_time) { \ + const char* substage_name = \ + GetVizBreakdownName(static_cast<VizBreakdown>(index)); \ + trace_func(start_time, end_time, substage_name); \ + } + +#define REPORT_VIZ_BREAKDOWN_TRACES(trace_func) \ + size_t start_to_buffer_available = \ + static_cast<size_t>(VizBreakdown::kSwapStartToBufferAvailable); \ + bool has_ready_timings = !!viz_breakdown_list_[start_to_buffer_available]; \ + for (size_t i = 0; i < start_to_buffer_available; i++) { \ + if (!viz_breakdown_list_[i]) \ + break; \ + \ + if (i == static_cast<size_t>(VizBreakdown::kSwapStartToSwapEnd) && \ + has_ready_timings) { \ + size_t latch_to_swap_end = \ + static_cast<size_t>(VizBreakdown::kLatchToSwapEnd); \ + for (size_t j = start_to_buffer_available; j <= latch_to_swap_end; \ + j++) { \ + REPORT_VIZ_TRACE_EVENT(viz_breakdown_list_[j]->first, \ + viz_breakdown_list_[j]->second, j, trace_func); \ + } \ + } else { \ + REPORT_VIZ_TRACE_EVENT(viz_breakdown_list_[i]->first, \ + viz_breakdown_list_[i]->second, i, trace_func); \ + } \ + } + } // namespace CompositorFrameReporter::CompositorFrameReporter( const ActiveTrackers& active_trackers, - const viz::BeginFrameId& id, - const base::TimeTicks frame_deadline, + const viz::BeginFrameArgs& args, LatencyUkmReporter* latency_ukm_reporter, bool should_report_metrics) - : frame_id_(id), - should_report_metrics_(should_report_metrics), + : should_report_metrics_(should_report_metrics), + args_(args), active_trackers_(active_trackers), - latency_ukm_reporter_(latency_ukm_reporter), - frame_deadline_(frame_deadline) {} + latency_ukm_reporter_(latency_ukm_reporter) {} std::unique_ptr<CompositorFrameReporter> CompositorFrameReporter::CopyReporterAtBeginImplStage() const { if (stage_history_.empty() || stage_history_.front().stage_type != StageType::kBeginImplFrameToSendBeginMainFrame || - !did_finish_impl_frame()) { + (!did_finish_impl_frame() && !did_not_produce_frame_time_.has_value())) { return nullptr; } auto new_reporter = std::make_unique<CompositorFrameReporter>( - active_trackers_, frame_id_, frame_deadline_, latency_ukm_reporter_, - should_report_metrics_); + active_trackers_, args_, latency_ukm_reporter_, should_report_metrics_); new_reporter->did_finish_impl_frame_ = did_finish_impl_frame_; new_reporter->impl_frame_finish_time_ = impl_frame_finish_time_; new_reporter->main_frame_abort_time_ = main_frame_abort_time_; @@ -241,6 +299,7 @@ CompositorFrameReporter::CopyReporterAtBeginImplStage() const { StageType::kBeginImplFrameToSendBeginMainFrame; new_reporter->current_stage_.start_time = stage_history_.front().start_time; new_reporter->set_tick_clock(tick_clock_); + new_reporter->SetDroppedFrameCounter(dropped_frame_counter_); return new_reporter; } @@ -349,36 +408,33 @@ void CompositorFrameReporter::TerminateReporter() { PopulateVizBreakdownList(); DCHECK_EQ(current_stage_.start_time, base::TimeTicks()); - const char* termination_status_str = nullptr; switch (frame_termination_status_) { case FrameTerminationStatus::kPresentedFrame: EnableReportType(FrameReportType::kNonDroppedFrame); - termination_status_str = "presented_frame"; - if (frame_deadline_ < frame_termination_time_) + ReportOffsetBetweenDeadlineAndPresentationTime(args_, + frame_termination_time_); + if (ComputeSafeDeadlineForFrame(args_) < frame_termination_time_) EnableReportType(FrameReportType::kMissedDeadlineFrame); break; case FrameTerminationStatus::kDidNotPresentFrame: EnableReportType(FrameReportType::kDroppedFrame); - termination_status_str = "did_not_present_frame"; break; case FrameTerminationStatus::kReplacedByNewReporter: EnableReportType(FrameReportType::kDroppedFrame); - termination_status_str = "replaced_by_new_reporter_at_same_stage"; break; case FrameTerminationStatus::kDidNotProduceFrame: - termination_status_str = "did_not_produce_frame"; if (!frame_skip_reason_.has_value() || frame_skip_reason() != FrameSkippedReason::kNoDamage) { EnableReportType(FrameReportType::kDroppedFrame); - termination_status_str = "dropped_frame"; } break; case FrameTerminationStatus::kUnknown: - termination_status_str = "terminated_before_ending"; break; } - ReportAllTraceEvents(termination_status_str); + ReportCompositorLatencyTraceEvents(); + if (TestReportType(FrameReportType::kNonDroppedFrame)) + ReportEventLatencyTraceEvents(); // Only report compositor latency histograms if the frame was produced. if (should_report_metrics_ && report_types_.any()) { @@ -394,6 +450,17 @@ void CompositorFrameReporter::TerminateReporter() { if (TestReportType(FrameReportType::kNonDroppedFrame)) ReportEventLatencyHistograms(); } + + if (dropped_frame_counter_) { + if (TestReportType(FrameReportType::kDroppedFrame)) { + dropped_frame_counter_->AddDroppedFrame(); + } else { + if (has_partial_update_) + dropped_frame_counter_->AddPartialFrame(); + else + dropped_frame_counter_->AddGoodFrame(); + } + } } void CompositorFrameReporter::EndCurrentStage(base::TimeTicks end_time) { @@ -421,8 +488,8 @@ void CompositorFrameReporter::ReportCompositorLatencyHistograms() const { FrameReportType report_type = static_cast<FrameReportType>(type); UMA_HISTOGRAM_ENUMERATION("CompositorLatency.Type", report_type); if (latency_ukm_reporter_) { - latency_ukm_reporter_->ReportLatencyUkm(report_type, stage_history_, - active_trackers_, viz_breakdown_); + latency_ukm_reporter_->ReportCompositorLatencyUkm( + report_type, stage_history_, active_trackers_, viz_breakdown_); } for (size_t fst_type = 0; fst_type < active_trackers_.size(); ++fst_type) { if (!active_trackers_.test(fst_type)) { @@ -510,9 +577,11 @@ void CompositorFrameReporter::ReportCompositorLatencyVizBreakdowns( #endif break; } + const base::TimeTicks start_time = viz_breakdown_list_[i]->first; + const base::TimeTicks end_time = viz_breakdown_list_[i]->second; ReportCompositorLatencyHistogram(frame_sequence_tracker_type, kVizBreakdownInitialIndex + i, - *viz_breakdown_list_[i]); + end_time - start_time); } } @@ -561,8 +630,8 @@ void CompositorFrameReporter::ReportEventLatencyHistograms() const { GetEventLatencyHistogramBaseName(event_metrics); const int event_type_index = static_cast<int>(event_metrics.type()); const int scroll_type_index = - event_metrics.scroll_input_type() - ? static_cast<int>(*event_metrics.scroll_input_type()) + event_metrics.scroll_type() + ? static_cast<int>(*event_metrics.scroll_type()) : 0; const int histogram_base_index = event_type_index * kEventLatencyScrollTypeCount + scroll_type_index; @@ -570,8 +639,7 @@ void CompositorFrameReporter::ReportEventLatencyHistograms() const { // For scroll events, report total latency up to gpu-swap-end. This is // useful in comparing new EventLatency metrics with LatencyInfo-based // scroll event latency metrics. - if (event_metrics.scroll_input_type() && - !viz_breakdown_.swap_timings.is_null()) { + if (event_metrics.scroll_type() && !viz_breakdown_.swap_timings.is_null()) { const base::TimeDelta swap_end_latency = viz_breakdown_.swap_timings.swap_end - event_metrics.time_stamp(); const std::string swap_end_histogram_name = @@ -586,57 +654,51 @@ void CompositorFrameReporter::ReportEventLatencyHistograms() const { base::HistogramBase::kUmaTargetedHistogramFlag)); } - const auto trace_id = TRACE_ID_LOCAL(&event_metrics); - TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP1( - "cc,input", "EventLatency", trace_id, event_metrics.time_stamp(), - "event", event_metrics.GetTypeName()); - // It is possible for an event to arrive in the compositor in the middle of // a frame (e.g. the browser received the event *after* renderer received a // begin-impl, and the event reached the compositor before that frame // ended). To handle such cases, find the first stage that happens after the // event's arrival in the browser. - size_t index = 0; - for (; index < stage_history_.size(); ++index) { - const auto& stage = stage_history_[index]; - if (stage.start_time > event_metrics.time_stamp()) { - const char stage_type_name[] = "BrowserToRendererCompositor"; - - const base::TimeDelta latency = - stage.start_time - event_metrics.time_stamp(); - const std::string histogram_name = - base::StrCat({histogram_base_name, ".", stage_type_name}); - STATIC_HISTOGRAM_POINTER_GROUP( - histogram_name, histogram_base_index, - kMaxEventLatencyHistogramBaseIndex, - AddTimeMicrosecondsGranularity(latency), - base::Histogram::FactoryGet( - histogram_name, kEventLatencyHistogramMin, - kEventLatencyHistogramMax, kEventLatencyHistogramBucketCount, - base::HistogramBase::kUmaTargetedHistogramFlag)); - - TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0( - "cc,input", stage_type_name, trace_id, event_metrics.time_stamp()); - TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0( - "cc,input", stage_type_name, trace_id, stage.start_time); - break; - } - } + auto stage_it = + std::find_if(stage_history_.begin(), stage_history_.end(), + [&event_metrics](const StageData& stage) { + return stage.start_time > event_metrics.time_stamp(); + }); + // TODO(crbug.com/1079116): Ideally, at least the start time of + // SubmitCompositorFrameToPresentationCompositorFrame stage should be + // greater than the event time stamp, but apparently, this is not always the + // case (see crbug.com/1093698). For now, skip to the next event in such + // cases. Hopefully, the work to reduce discrepancies between the new + // EventLatency and the old Event.Latency metrics would fix this issue. If + // not, we need to reconsider investigating this issue. + if (stage_it == stage_history_.end()) + continue; - for (; index < stage_history_.size(); ++index) { - const auto& stage = stage_history_[index]; + const base::TimeDelta b2r_latency = + stage_it->start_time - event_metrics.time_stamp(); + const std::string b2r_histogram_name = + histogram_base_name + ".BrowserToRendererCompositor"; + STATIC_HISTOGRAM_POINTER_GROUP( + b2r_histogram_name, histogram_base_index, + kMaxEventLatencyHistogramBaseIndex, + AddTimeMicrosecondsGranularity(b2r_latency), + base::Histogram::FactoryGet( + b2r_histogram_name, kEventLatencyHistogramMin, + kEventLatencyHistogramMax, kEventLatencyHistogramBucketCount, + base::HistogramBase::kUmaTargetedHistogramFlag)); + for (; stage_it != stage_history_.end(); ++stage_it) { // Total latency is calculated since the event timestamp. const base::TimeTicks start_time = - stage.stage_type == StageType::kTotalLatency + stage_it->stage_type == StageType::kTotalLatency ? event_metrics.time_stamp() - : stage.start_time; - const base::TimeDelta latency = stage.end_time - start_time; - const int stage_type_index = static_cast<int>(stage.stage_type); + : stage_it->start_time; + const base::TimeDelta latency = stage_it->end_time - start_time; + const int stage_type_index = static_cast<int>(stage_it->stage_type); ReportEventLatencyHistogram(histogram_base_index, histogram_base_name, stage_type_index, latency); - switch (stage.stage_type) { + switch (stage_it->stage_type) { case StageType::kSendBeginMainFrameToCommit: ReportEventLatencyBlinkBreakdowns(histogram_base_index, histogram_base_name); @@ -648,18 +710,12 @@ void CompositorFrameReporter::ReportEventLatencyHistograms() const { default: break; } - - if (stage.stage_type != StageType::kTotalLatency) { - TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0( - "cc,input", GetStageName(stage_type_index), trace_id, - stage.start_time); - TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0( - "cc,input", GetStageName(stage_type_index), trace_id, - stage.end_time); - } } - TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0( - "cc,input", "EventLatency", trace_id, frame_termination_time_); + } + + if (latency_ukm_reporter_) { + latency_ukm_reporter_->ReportEventLatencyUkm( + events_metrics_, stage_history_, viz_breakdown_); } } @@ -685,9 +741,11 @@ void CompositorFrameReporter::ReportEventLatencyVizBreakdowns( #endif break; } + const base::TimeTicks start_time = viz_breakdown_list_[i]->first; + const base::TimeTicks end_time = viz_breakdown_list_[i]->second; ReportEventLatencyHistogram(histogram_base_index, histogram_base_name, kVizBreakdownInitialIndex + i, - *viz_breakdown_list_[i]); + end_time - start_time); } } @@ -710,32 +768,31 @@ void CompositorFrameReporter::ReportEventLatencyHistogram( base::HistogramBase::kUmaTargetedHistogramFlag)); } -void CompositorFrameReporter::ReportVizBreakdownTrace( - VizBreakdown substage, - const base::TimeTicks start_time, - const base::TimeTicks end_time) const { - // Do not report events with negative time span. - if (end_time < start_time) - return; - - const char* stage_name = GetVizBreakdownName(substage); - TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0( - "cc,benchmark", stage_name, TRACE_ID_LOCAL(this), start_time); - - TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0( - "cc,benchmark", stage_name, TRACE_ID_LOCAL(this), end_time); -} - -void CompositorFrameReporter::ReportAllTraceEvents( - const char* termination_status_str) const { +void CompositorFrameReporter::ReportCompositorLatencyTraceEvents() const { if (stage_history_.empty()) return; - const auto trace_id = TRACE_ID_LOCAL(this); - const auto& startstage = stage_history_.front(); - TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP1( - "cc,benchmark", "PipelineReporter", trace_id, startstage.start_time, - "frame_id", frame_id_.ToString()); + const auto trace_track = perfetto::Track(reinterpret_cast<uint64_t>(this)); + TRACE_EVENT_BEGIN( + "cc,benchmark", "PipelineReporter", trace_track, + stage_history_.front().start_time, [&](perfetto::EventContext context) { + using perfetto::protos::pbzero::ChromeFrameReporter; + bool frame_dropped = TestReportType(FrameReportType::kDroppedFrame); + ChromeFrameReporter::State state; + if (frame_dropped) { + state = ChromeFrameReporter::STATE_DROPPED; + } else if (frame_termination_status_ == + FrameTerminationStatus::kDidNotProduceFrame) { + state = ChromeFrameReporter::STATE_NO_UPDATE_DESIRED; + } else { + state = ChromeFrameReporter::STATE_PRESENTED_ALL; + } + auto* reporter = context.event()->set_chrome_frame_reporter(); + reporter->set_state(state); + reporter->set_frame_source(args_.frame_id.source_id); + reporter->set_frame_sequence(args_.frame_id.sequence_number); + // TODO(crbug.com/1086974): Set 'drop reason' if applicable. + }); // The trace-viewer cannot seem to handle a single child-event that has the // same start/end timestamps as the parent-event. So avoid adding the @@ -750,50 +807,89 @@ void CompositorFrameReporter::ReportAllTraceEvents( DCHECK_GE(stage.end_time, stage.start_time); if (stage.start_time == stage.end_time) continue; - const char* name = GetStageName(stage_type_index); + const char* stage_name = GetStageName(stage_type_index); + TRACE_EVENT_BEGIN("cc,benchmark", stage_name, trace_track, + stage.start_time); + if (stage.stage_type == + StageType::kSubmitCompositorFrameToPresentationCompositorFrame) { + REPORT_VIZ_BREAKDOWN_TRACES([&](base::TimeTicks start_time, + base::TimeTicks end_time, + const char* substage_name) { + TRACE_EVENT_BEGIN("cc,benchmark", substage_name, trace_track, + start_time); + TRACE_EVENT_END("cc,benchmark", trace_track, end_time); + }); + } + TRACE_EVENT_END("cc,benchmark", trace_track, stage.end_time); + } + } + + TRACE_EVENT_END("cc,benchmark", trace_track, frame_termination_time_); +} + +void CompositorFrameReporter::ReportEventLatencyTraceEvents() const { + for (const EventMetrics& event_metrics : events_metrics_) { + const auto trace_id = TRACE_ID_LOCAL(&event_metrics); + TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP1( + "cc,input", "EventLatency", trace_id, event_metrics.time_stamp(), + "event", event_metrics.GetTypeName()); + + // Find the first stage that happens after the event's arrival in the + // browser. + auto stage_it = + std::find_if(stage_history_.begin(), stage_history_.end(), + [&event_metrics](const StageData& stage) { + return stage.start_time > event_metrics.time_stamp(); + }); + // TODO(crbug.com/1079116): Ideally, at least the start time of + // SubmitCompositorFrameToPresentationCompositorFrame stage should be + // greater than the event time stamp, but apparently, this is not always the + // case (see crbug.com/1093698). For now, skip to the next event in such + // cases. Hopefully, the work to reduce discrepancies between the new + // EventLatency and the old Event.Latency metrics would fix this issue. If + // not, we need to reconsider investigating this issue. + if (stage_it == stage_history_.end()) + continue; + + TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0( + "cc,input", "BrowserToRendererCompositor", trace_id, + event_metrics.time_stamp()); + TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0( + "cc,input", "BrowserToRendererCompositor", trace_id, + stage_it->start_time); + + for (; stage_it != stage_history_.end(); ++stage_it) { + const int stage_type_index = static_cast<int>(stage_it->stage_type); + CHECK_LT(stage_type_index, static_cast<int>(StageType::kStageTypeCount)); + CHECK_GE(stage_type_index, 0); + if (stage_it->start_time >= frame_termination_time_) + break; + DCHECK_GE(stage_it->end_time, stage_it->start_time); + if (stage_it->start_time == stage_it->end_time) + continue; + const char* stage_name = GetStageName(stage_type_index); + TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0( - "cc,benchmark", name, trace_id, stage.start_time); + "cc,input", stage_name, trace_id, stage_it->start_time); - if (stage.stage_type == + if (stage_it->stage_type == StageType::kSubmitCompositorFrameToPresentationCompositorFrame) { - const struct { - VizBreakdown stage; - base::TimeTicks start_time; - base::TimeTicks end_time; - } sub_stages[] = { - {VizBreakdown::kSubmitToReceiveCompositorFrame, stage.start_time, - viz_breakdown_.received_compositor_frame_timestamp}, - {VizBreakdown::kReceivedCompositorFrameToStartDraw, - viz_breakdown_.received_compositor_frame_timestamp, - viz_breakdown_.draw_start_timestamp}, - {VizBreakdown::kStartDrawToSwapStart, - viz_breakdown_.draw_start_timestamp, - viz_breakdown_.swap_timings.swap_start}, - {VizBreakdown::kSwapStartToSwapEnd, - viz_breakdown_.swap_timings.swap_start, - viz_breakdown_.swap_timings.swap_end}, - {VizBreakdown::kSwapEndToPresentationCompositorFrame, - viz_breakdown_.swap_timings.swap_end, - viz_breakdown_.presentation_feedback.timestamp}}; - for (const auto& sub : sub_stages) { - if (sub.start_time.is_null() || sub.end_time.is_null()) - break; - ReportVizBreakdownTrace(sub.stage, sub.start_time, sub.end_time); - } + REPORT_VIZ_BREAKDOWN_TRACES([&](base::TimeTicks start_time, + base::TimeTicks end_time, + const char* substage_name) { + TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0( + "cc,input", substage_name, trace_id, start_time); + TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0( + "cc,input", substage_name, trace_id, end_time); + }); } - TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0("cc,benchmark", name, - trace_id, stage.end_time); + TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0( + "cc,input", stage_name, trace_id, stage_it->end_time); } + TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0( + "cc,input", "EventLatency", trace_id, frame_termination_time_); } - - const char* submission_status_str = - TestReportType(FrameReportType::kDroppedFrame) ? "dropped_frame" - : "non_dropped_frame"; - TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP2( - "cc,benchmark", "PipelineReporter", trace_id, frame_termination_time_, - "termination_status", termination_status_str, - "compositor_frame_submission_status", submission_status_str); } void CompositorFrameReporter::PopulateBlinkBreakdownList() { @@ -839,29 +935,47 @@ void CompositorFrameReporter::PopulateVizBreakdownList() { } viz_breakdown_list_[static_cast<int>( VizBreakdown::kSubmitToReceiveCompositorFrame)] = - viz_breakdown_.received_compositor_frame_timestamp - viz_start_time_; + std::make_pair(viz_start_time_, + viz_breakdown_.received_compositor_frame_timestamp); if (viz_breakdown_.draw_start_timestamp.is_null()) return; viz_breakdown_list_[static_cast<int>( VizBreakdown::kReceivedCompositorFrameToStartDraw)] = - viz_breakdown_.draw_start_timestamp - - viz_breakdown_.received_compositor_frame_timestamp; + std::make_pair(viz_breakdown_.received_compositor_frame_timestamp, + viz_breakdown_.draw_start_timestamp); if (viz_breakdown_.swap_timings.is_null()) return; viz_breakdown_list_[static_cast<int>(VizBreakdown::kStartDrawToSwapStart)] = - viz_breakdown_.swap_timings.swap_start - - viz_breakdown_.draw_start_timestamp; + std::make_pair(viz_breakdown_.draw_start_timestamp, + viz_breakdown_.swap_timings.swap_start); viz_breakdown_list_[static_cast<int>(VizBreakdown::kSwapStartToSwapEnd)] = - viz_breakdown_.swap_timings.swap_end - - viz_breakdown_.swap_timings.swap_start; + std::make_pair(viz_breakdown_.swap_timings.swap_start, + viz_breakdown_.swap_timings.swap_end); viz_breakdown_list_[static_cast<int>( VizBreakdown::kSwapEndToPresentationCompositorFrame)] = - viz_breakdown_.presentation_feedback.timestamp - - viz_breakdown_.swap_timings.swap_end; + std::make_pair(viz_breakdown_.swap_timings.swap_end, + viz_breakdown_.presentation_feedback.timestamp); + + if (viz_breakdown_.presentation_feedback.ready_timestamp.is_null()) + return; + viz_breakdown_list_[static_cast<int>( + VizBreakdown::kSwapStartToBufferAvailable)] = + std::make_pair(viz_breakdown_.swap_timings.swap_start, + viz_breakdown_.presentation_feedback.available_timestamp); + viz_breakdown_list_[static_cast<int>( + VizBreakdown::kBufferAvailableToBufferReady)] = + std::make_pair(viz_breakdown_.presentation_feedback.available_timestamp, + viz_breakdown_.presentation_feedback.ready_timestamp); + viz_breakdown_list_[static_cast<int>(VizBreakdown::kBufferReadyToLatch)] = + std::make_pair(viz_breakdown_.presentation_feedback.ready_timestamp, + viz_breakdown_.presentation_feedback.latch_timestamp); + viz_breakdown_list_[static_cast<int>(VizBreakdown::kLatchToSwapEnd)] = + std::make_pair(viz_breakdown_.presentation_feedback.latch_timestamp, + viz_breakdown_.swap_timings.swap_end); } base::TimeDelta CompositorFrameReporter::SumOfStageHistory() const { diff --git a/chromium/cc/metrics/compositor_frame_reporter.h b/chromium/cc/metrics/compositor_frame_reporter.h index 9cae550ba76..2ff3b986489 100644 --- a/chromium/cc/metrics/compositor_frame_reporter.h +++ b/chromium/cc/metrics/compositor_frame_reporter.h @@ -7,6 +7,7 @@ #include <bitset> #include <memory> +#include <utility> #include <vector> #include "base/optional.h" @@ -26,6 +27,7 @@ struct FrameTimingDetails; } namespace cc { +class DroppedFrameCounter; class LatencyUkmReporter; // This is used for tracing and reporting the duration of pipeline stages within @@ -93,6 +95,13 @@ class CC_EXPORT CompositorFrameReporter { kStartDrawToSwapStart = 2, kSwapStartToSwapEnd = 3, kSwapEndToPresentationCompositorFrame = 4, + + // This is a breakdown of SwapStartToSwapEnd stage which is optionally + // recorded if querying these timestamps is supported by the platform. + kSwapStartToBufferAvailable = 5, + kBufferAvailableToBufferReady = 6, + kBufferReadyToLatch = 7, + kLatchToSwapEnd = 8, kBreakdownCount }; @@ -111,7 +120,7 @@ class CC_EXPORT CompositorFrameReporter { kBreakdownCount }; - struct StageData { + struct CC_EXPORT StageData { StageType stage_type; base::TimeTicks start_time; base::TimeTicks end_time; @@ -127,8 +136,7 @@ class CC_EXPORT CompositorFrameReporter { std::bitset<static_cast<size_t>(FrameSequenceTrackerType::kMaxType)>; CompositorFrameReporter(const ActiveTrackers& active_trackers, - const viz::BeginFrameId& id, - const base::TimeTicks frame_deadline, + const viz::BeginFrameArgs& args, LatencyUkmReporter* latency_ukm_reporter, bool should_report_metrics); ~CompositorFrameReporter(); @@ -139,8 +147,6 @@ class CC_EXPORT CompositorFrameReporter { std::unique_ptr<CompositorFrameReporter> CopyReporterAtBeginImplStage() const; - const viz::BeginFrameId frame_id_; - // Note that the started stage may be reported to UMA. If the histogram is // intended to be reported then the histograms.xml file must be updated too. void StartStage(StageType stage_type, base::TimeTicks start_time); @@ -183,6 +189,13 @@ class CC_EXPORT CompositorFrameReporter { tick_clock_ = tick_clock; } + void SetDroppedFrameCounter(DroppedFrameCounter* counter) { + dropped_frame_counter_ = counter; + } + void SetHasPartialUpdate() { has_partial_update_ = true; } + + const viz::BeginFrameId& frame_id() const { return args_.frame_id; } + private: void TerminateReporter(); void EndCurrentStage(base::TimeTicks end_time); @@ -213,15 +226,8 @@ class CC_EXPORT CompositorFrameReporter { int stage_type_index, base::TimeDelta latency) const; - // Generate a trace event corresponding to a Viz breakdown under - // SubmitCompositorFrameToPresentationCompositorFrame stage in - // PipelineReporter. This function only generates trace events and does not - // report histograms. - void ReportVizBreakdownTrace(VizBreakdown substage, - const base::TimeTicks start_time, - const base::TimeTicks end_time) const; - - void ReportAllTraceEvents(const char* termination_status_str) const; + void ReportCompositorLatencyTraceEvents() const; + void ReportEventLatencyTraceEvents() const; void EnableReportType(FrameReportType report_type) { report_types_.set(static_cast<size_t>(report_type)); @@ -239,6 +245,7 @@ class CC_EXPORT CompositorFrameReporter { base::TimeTicks Now() const; const bool should_report_metrics_; + const viz::BeginFrameArgs args_; StageData current_stage_; @@ -249,7 +256,7 @@ class CC_EXPORT CompositorFrameReporter { viz::FrameTimingDetails viz_breakdown_; base::TimeTicks viz_start_time_; - base::Optional<base::TimeDelta> + base::Optional<std::pair<base::TimeTicks, base::TimeTicks>> viz_breakdown_list_[static_cast<int>(VizBreakdown::kBreakdownCount)]; // Stage data is recorded here. On destruction these stages will be reported @@ -277,7 +284,6 @@ class CC_EXPORT CompositorFrameReporter { // The time that work on Impl frame is finished. It's only valid if the // reporter is in a stage other than begin impl frame. base::TimeTicks impl_frame_finish_time_; - base::TimeTicks frame_deadline_; // The timestamp of when the frame was marked as not having produced a frame // (through a call to DidNotProduceFrame()). @@ -286,7 +292,11 @@ class CC_EXPORT CompositorFrameReporter { base::Optional<base::TimeTicks> main_frame_abort_time_; const base::TickClock* tick_clock_ = base::DefaultTickClock::GetInstance(); + + DroppedFrameCounter* dropped_frame_counter_ = nullptr; + bool has_partial_update_ = false; }; + } // namespace cc #endif // CC_METRICS_COMPOSITOR_FRAME_REPORTER_H_" diff --git a/chromium/cc/metrics/compositor_frame_reporter_unittest.cc b/chromium/cc/metrics/compositor_frame_reporter_unittest.cc index afd8830e3e3..414655412dd 100644 --- a/chromium/cc/metrics/compositor_frame_reporter_unittest.cc +++ b/chromium/cc/metrics/compositor_frame_reporter_unittest.cc @@ -30,8 +30,7 @@ class CompositorFrameReporterTest : public testing::Test { CompositorFrameReporterTest() : pipeline_reporter_(std::make_unique<CompositorFrameReporter>( CompositorFrameReporter::ActiveTrackers(), - viz::BeginFrameId(), - base::TimeTicks() + base::TimeDelta::FromMilliseconds(16), + viz::BeginFrameArgs(), nullptr, /*should_report_metrics=*/true)) { pipeline_reporter_->set_tick_clock(&test_tick_clock_); diff --git a/chromium/cc/metrics/compositor_frame_reporting_controller.cc b/chromium/cc/metrics/compositor_frame_reporting_controller.cc index cd0ae991606..b19d869c968 100644 --- a/chromium/cc/metrics/compositor_frame_reporting_controller.cc +++ b/chromium/cc/metrics/compositor_frame_reporting_controller.cc @@ -68,11 +68,12 @@ void CompositorFrameReportingController::WillBeginImplFrame( reporter->did_not_produce_frame_time()); } auto reporter = std::make_unique<CompositorFrameReporter>( - active_trackers_, args.frame_id, args.frame_time + (args.interval * 1.5), - latency_ukm_reporter_.get(), should_report_metrics_); + active_trackers_, args, latency_ukm_reporter_.get(), + should_report_metrics_); reporter->set_tick_clock(tick_clock_); reporter->StartStage(StageType::kBeginImplFrameToSendBeginMainFrame, begin_time); + reporter->SetDroppedFrameCounter(dropped_frame_counter_); reporters_[PipelineStage::kBeginImplFrame] = std::move(reporter); } @@ -83,7 +84,7 @@ void CompositorFrameReportingController::WillBeginMainFrame( // C++20 feature. DCHECK_NE(reporters_[PipelineStage::kBeginMainFrame].get(), reporters_[PipelineStage::kBeginImplFrame].get()); - DCHECK_EQ(reporters_[PipelineStage::kBeginImplFrame]->frame_id_, + DCHECK_EQ(reporters_[PipelineStage::kBeginImplFrame]->frame_id(), args.frame_id); reporters_[PipelineStage::kBeginImplFrame]->StartStage( StageType::kSendBeginMainFrameToCommit, Now()); @@ -94,11 +95,11 @@ void CompositorFrameReportingController::WillBeginMainFrame( // beginMain frame before next BeginImplFrame (Not reached the ImplFrame // deadline yet). So will start a new reporter at BeginMainFrame. auto reporter = std::make_unique<CompositorFrameReporter>( - active_trackers_, args.frame_id, - args.frame_time + (args.interval * 1.5), latency_ukm_reporter_.get(), + active_trackers_, args, latency_ukm_reporter_.get(), should_report_metrics_); reporter->set_tick_clock(tick_clock_); reporter->StartStage(StageType::kSendBeginMainFrameToCommit, Now()); + reporter->SetDroppedFrameCounter(dropped_frame_counter_); reporters_[PipelineStage::kBeginMainFrame] = std::move(reporter); } } @@ -107,7 +108,7 @@ void CompositorFrameReportingController::BeginMainFrameAborted( const viz::BeginFrameId& id) { auto& reporter = reporters_[PipelineStage::kBeginMainFrame]; DCHECK(reporter); - DCHECK_EQ(reporter->frame_id_, id); + DCHECK_EQ(reporter->frame_id(), id); reporter->OnAbortBeginMainFrame(Now()); } @@ -169,7 +170,7 @@ void CompositorFrameReportingController::DidSubmitCompositorFrame( // |impl_reporter| is guaranteed to be set, and |main_reporter| will not be // set. if (is_activated_frame_new) { - DCHECK_EQ(reporters_[PipelineStage::kActivate]->frame_id_, + DCHECK_EQ(reporters_[PipelineStage::kActivate]->frame_id(), last_activated_frame_id); // The reporter in activate state can be submitted main_reporter = std::move(reporters_[PipelineStage::kActivate]); @@ -205,6 +206,7 @@ void CompositorFrameReportingController::DidSubmitCompositorFrame( if (reporter) { reporter->StartStage(StageType::kEndActivateToSubmitCompositorFrame, reporter->impl_frame_finish_time()); + reporter->SetHasPartialUpdate(); impl_reporter = std::move(reporter); } } @@ -220,7 +222,7 @@ void CompositorFrameReportingController::DidSubmitCompositorFrame( DCHECK(main_reporter); // If there are impl events, there must be a reporter with // |current_frame_id|. - DCHECK_EQ(main_reporter->frame_id_, current_frame_id); + DCHECK_EQ(main_reporter->frame_id(), current_frame_id); events_metrics.main_event_metrics.reserve( events_metrics.main_event_metrics.size() + events_metrics.impl_event_metrics.size()); @@ -255,7 +257,7 @@ void CompositorFrameReportingController::DidNotProduceFrame( const viz::BeginFrameId& id, FrameSkippedReason skip_reason) { for (auto& stage_reporter : reporters_) { - if (stage_reporter && stage_reporter->frame_id_ == id) { + if (stage_reporter && stage_reporter->frame_id() == id) { // The reporter will be flagged and terminated when replaced by another // reporter. The reporter is not terminated immediately here because it // can still end up producing a frame afterwards. For example, if the @@ -264,7 +266,20 @@ void CompositorFrameReportingController::DidNotProduceFrame( // BeginMain stage, but the main-thread can make updates, which can be // submitted with the next frame. stage_reporter->OnDidNotProduceFrame(skip_reason); - return; + break; + } + } + + // If the compositor has no updates, and the main-thread has not responded to + // the begin-main-frame yet, then it is essentially a dropped frame. To handle + // this case, keep the reporter for the main-thread, but recreate a reporter + // for the dropped-frame. + if (skip_reason == FrameSkippedReason::kWaitingOnMain) { + auto reporter = RestoreReporterAtBeginImpl(id); + if (reporter) { + reporter->OnDidNotProduceFrame(skip_reason); + reporter->TerminateFrame(FrameTerminationStatus::kDidNotProduceFrame, + Now()); } } } @@ -272,7 +287,7 @@ void CompositorFrameReportingController::DidNotProduceFrame( void CompositorFrameReportingController::OnFinishImplFrame( const viz::BeginFrameId& id) { for (auto& reporter : reporters_) { - if (reporter && reporter->frame_id_ == id) { + if (reporter && reporter->frame_id() == id) { reporter->OnFinishImplFrame(Now()); return; } @@ -288,8 +303,10 @@ void CompositorFrameReportingController::DidPresentCompositorFrame( break; auto termination_status = FrameTerminationStatus::kPresentedFrame; - if (submitted_frame->frame_token != frame_token) + if (submitted_frame->frame_token != frame_token || + details.presentation_feedback.failed()) { termination_status = FrameTerminationStatus::kDidNotPresentFrame; + } submitted_frame->reporter->SetVizBreakdown(details); submitted_frame->reporter->TerminateFrame( @@ -356,7 +373,7 @@ bool CompositorFrameReportingController::CanSubmitImplFrame( if (!reporters_[PipelineStage::kBeginImplFrame]) return false; auto& reporter = reporters_[PipelineStage::kBeginImplFrame]; - return (reporter->frame_id_ == id && reporter->did_finish_impl_frame()); + return (reporter->frame_id() == id && reporter->did_finish_impl_frame()); } bool CompositorFrameReportingController::CanSubmitMainFrame( @@ -364,7 +381,7 @@ bool CompositorFrameReportingController::CanSubmitMainFrame( if (!reporters_[PipelineStage::kBeginMainFrame]) return false; auto& reporter = reporters_[PipelineStage::kBeginMainFrame]; - return (reporter->frame_id_ == id && reporter->did_finish_impl_frame() && + return (reporter->frame_id() == id && reporter->did_finish_impl_frame() && reporter->did_abort_main_frame()); } @@ -373,15 +390,15 @@ CompositorFrameReportingController::RestoreReporterAtBeginImpl( const viz::BeginFrameId& id) { auto& main_reporter = reporters_[PipelineStage::kBeginMainFrame]; auto& commit_reporter = reporters_[PipelineStage::kCommit]; - if (main_reporter && main_reporter->frame_id_ == id) + if (main_reporter && main_reporter->frame_id() == id) return main_reporter->CopyReporterAtBeginImplStage(); - if (commit_reporter && commit_reporter->frame_id_ == id) + if (commit_reporter && commit_reporter->frame_id() == id) return commit_reporter->CopyReporterAtBeginImplStage(); return nullptr; } void CompositorFrameReportingController::SetUkmManager(UkmManager* manager) { - latency_ukm_reporter_->SetUkmManager(manager); + latency_ukm_reporter_->set_ukm_manager(manager); } } // namespace cc diff --git a/chromium/cc/metrics/compositor_frame_reporting_controller.h b/chromium/cc/metrics/compositor_frame_reporting_controller.h index fb6b219ce27..2659e92b59d 100644 --- a/chromium/cc/metrics/compositor_frame_reporting_controller.h +++ b/chromium/cc/metrics/compositor_frame_reporting_controller.h @@ -20,6 +20,7 @@ struct FrameTimingDetails; } namespace cc { +class DroppedFrameCounter; class UkmManager; struct BeginMainFrameMetrics; @@ -85,6 +86,10 @@ class CC_EXPORT CompositorFrameReportingController { std::unique_ptr<CompositorFrameReporter>* reporters() { return reporters_; } + void SetDroppedFrameCounter(DroppedFrameCounter* counter) { + dropped_frame_counter_ = counter; + } + protected: struct SubmittedCompositorFrame { uint32_t frame_token; @@ -98,6 +103,9 @@ class CC_EXPORT CompositorFrameReportingController { base::TimeTicks Now() const; bool HasReporterAt(PipelineStage stage) const; + bool next_activate_has_invalidation() const { + return next_activate_has_invalidation_; + } private: void AdvanceReporterStage(PipelineStage start, PipelineStage target); @@ -128,6 +136,8 @@ class CC_EXPORT CompositorFrameReportingController { base::circular_deque<SubmittedCompositorFrame> submitted_compositor_frames_; const base::TickClock* tick_clock_ = base::DefaultTickClock::GetInstance(); + + DroppedFrameCounter* dropped_frame_counter_ = nullptr; }; } // namespace cc diff --git a/chromium/cc/metrics/compositor_frame_reporting_controller_unittest.cc b/chromium/cc/metrics/compositor_frame_reporting_controller_unittest.cc index ca70577d732..68bab0a45c1 100644 --- a/chromium/cc/metrics/compositor_frame_reporting_controller_unittest.cc +++ b/chromium/cc/metrics/compositor_frame_reporting_controller_unittest.cc @@ -524,13 +524,13 @@ TEST_F(CompositorFrameReportingControllerTest, args_3.interval, 0}; reporting_controller_.DidPresentCompositorFrame(1, details); - // Frame for |args_2| was dropped waiting on the main-thread. + // Frames for |args_1| and |args_2| were dropped waiting on the main-thread. histogram_tester.ExpectBucketCount( "CompositorLatency.Type", - CompositorFrameReporter::FrameReportType::kDroppedFrame, 1); + CompositorFrameReporter::FrameReportType::kDroppedFrame, 2); - // Frames for |args_1| and |args_3| were presented, although |args_1| missed - // its deadline. + // Frames for |args_1| and |args_3| were presented with |args_3|, and |args_1| + // missed its deadline. histogram_tester.ExpectBucketCount( "CompositorLatency.Type", CompositorFrameReporter::FrameReportType::kNonDroppedFrame, 2); diff --git a/chromium/cc/metrics/compositor_timing_history.cc b/chromium/cc/metrics/compositor_timing_history.cc index d0cc7d44262..99873a0152f 100644 --- a/chromium/cc/metrics/compositor_timing_history.cc +++ b/chromium/cc/metrics/compositor_timing_history.cc @@ -46,10 +46,6 @@ class CompositorTimingHistory::UMAReporter { // crbug.com/758439: the following functions are used to report timing in // certain conditions targeting blink / compositor animations. // Only the renderer would get the meaningful data. - virtual void AddDrawIntervalWithCompositedAnimations( - base::TimeDelta duration) = 0; - virtual void AddDrawIntervalWithMainThreadAnimations( - base::TimeDelta duration) = 0; virtual void AddDrawIntervalWithCustomPropertyAnimations( base::TimeDelta duration) = 0; }; @@ -310,18 +306,6 @@ class RendererUMAReporter : public CompositorTimingHistory::UMAReporter { interval); } - void AddDrawIntervalWithCompositedAnimations( - base::TimeDelta interval) override { - UMA_HISTOGRAM_CUSTOM_TIMES_VSYNC_ALIGNED( - "Scheduling.Renderer.DrawIntervalWithCompositedAnimations", interval); - } - - void AddDrawIntervalWithMainThreadAnimations( - base::TimeDelta interval) override { - UMA_HISTOGRAM_CUSTOM_TIMES_VSYNC_ALIGNED( - "Scheduling.Renderer.DrawIntervalWithMainThreadAnimations", interval); - } - void AddDrawIntervalWithCustomPropertyAnimations( base::TimeDelta interval) override { UMA_HISTOGRAM_CUSTOM_TIMES_VSYNC_ALIGNED( @@ -396,12 +380,6 @@ class BrowserUMAReporter : public CompositorTimingHistory::UMAReporter { // browser rendering fps is not at 60. void AddDrawInterval(base::TimeDelta interval) override {} - void AddDrawIntervalWithCompositedAnimations( - base::TimeDelta interval) override {} - - void AddDrawIntervalWithMainThreadAnimations( - base::TimeDelta interval) override {} - void AddDrawIntervalWithCustomPropertyAnimations( base::TimeDelta interval) override {} @@ -456,10 +434,6 @@ class NullUMAReporter : public CompositorTimingHistory::UMAReporter { ~NullUMAReporter() override = default; void AddBeginMainFrameIntervalCritical(base::TimeDelta interval) override {} void AddDrawInterval(base::TimeDelta interval) override {} - void AddDrawIntervalWithCompositedAnimations( - base::TimeDelta inverval) override {} - void AddDrawIntervalWithMainThreadAnimations( - base::TimeDelta inverval) override {} void AddDrawIntervalWithCustomPropertyAnimations( base::TimeDelta inverval) override {} void AddBeginImplFrameLatency(base::TimeDelta delta) override {} @@ -861,10 +835,6 @@ void CompositorTimingHistory::WillDraw() { } void CompositorTimingHistory::DidDraw(bool used_new_active_tree, - size_t composited_animations_count, - size_t main_thread_animations_count, - bool current_frame_had_raf, - bool next_frame_has_pending_raf, bool has_custom_property_animations) { DCHECK_NE(base::TimeTicks(), draw_start_time_); base::TimeTicks draw_end_time = Now(); @@ -898,42 +868,16 @@ void CompositorTimingHistory::DidDraw(bool used_new_active_tree, TRACE_ID_LOCAL(g_num_long_draw_intervals), draw_end_time); g_num_long_draw_intervals++; } - if (composited_animations_count > 0 && - previous_frame_had_composited_animations_) - uma_reporter_->AddDrawIntervalWithCompositedAnimations(draw_interval); if (has_custom_property_animations && previous_frame_had_custom_property_animations_) uma_reporter_->AddDrawIntervalWithCustomPropertyAnimations(draw_interval); } - previous_frame_had_composited_animations_ = composited_animations_count > 0; previous_frame_had_custom_property_animations_ = has_custom_property_animations; draw_end_time_prev_ = draw_end_time; - if (used_new_active_tree) { - bool current_main_frame_had_visual_update = - main_thread_animations_count > 0 || current_frame_had_raf; - bool previous_main_frame_had_visual_update = - previous_frame_had_main_thread_animations_ || previous_frame_had_raf_; - if (current_main_frame_had_visual_update && - previous_main_frame_had_visual_update) { - base::TimeDelta draw_interval = - draw_end_time - new_active_tree_draw_end_time_prev_; - uma_reporter_->AddDrawIntervalWithMainThreadAnimations(draw_interval); - } - previous_frame_had_main_thread_animations_ = - main_thread_animations_count > 0; - // It's possible that two consecutive main frames both run a rAF but are - // separated by idle time (for example: calling requestAnimationFrame from a - // setInterval function, with nothing else producing a main frame - // in-between). To avoid incorrectly counting those cases as long draw - // intervals, we only update previous_frame_had_raf_ if the current frame - // also already has a future raf scheduled. - previous_frame_had_raf_ = - current_frame_had_raf && next_frame_has_pending_raf; - + if (used_new_active_tree) new_active_tree_draw_end_time_prev_ = draw_end_time; - } draw_start_time_ = base::TimeTicks(); } diff --git a/chromium/cc/metrics/compositor_timing_history.h b/chromium/cc/metrics/compositor_timing_history.h index 1d265a8419a..a4ff2503787 100644 --- a/chromium/cc/metrics/compositor_timing_history.h +++ b/chromium/cc/metrics/compositor_timing_history.h @@ -91,10 +91,6 @@ class CC_EXPORT CompositorTimingHistory { void DidActivate(); void WillDraw(); void DidDraw(bool used_new_active_tree, - size_t composited_animations_count, - size_t main_thread_animations_count, - bool current_frame_had_raf, - bool next_frame_has_pending_raf, bool has_custom_property_animations); void DidSubmitCompositorFrame( uint32_t frame_token, @@ -140,7 +136,6 @@ class CC_EXPORT CompositorTimingHistory { bool compositor_drawing_continuously_; base::TimeTicks begin_main_frame_end_time_prev_; base::TimeTicks new_active_tree_draw_end_time_prev_; - base::TimeTicks new_active_tree_draw_end_time_prev_committing_continuously_; base::TimeTicks draw_end_time_prev_; // If you add any history here, please remember to reset it in @@ -179,10 +174,7 @@ class CC_EXPORT CompositorTimingHistory { CompositorFrameReportingController* compositor_frame_reporting_controller_; // Used only for reporting animation targeted UMA. - bool previous_frame_had_composited_animations_ = false; - bool previous_frame_had_main_thread_animations_ = false; bool previous_frame_had_custom_property_animations_ = false; - bool previous_frame_had_raf_ = false; TreePriority tree_priority_ = SAME_PRIORITY_FOR_BOTH_TREES; }; diff --git a/chromium/cc/metrics/compositor_timing_history_unittest.cc b/chromium/cc/metrics/compositor_timing_history_unittest.cc index b5b713355d2..82c35dcad08 100644 --- a/chromium/cc/metrics/compositor_timing_history_unittest.cc +++ b/chromium/cc/metrics/compositor_timing_history_unittest.cc @@ -4,7 +4,6 @@ #include "cc/metrics/compositor_timing_history.h" -#include "base/test/metrics/histogram_tester.h" #include "cc/debug/rendering_stats_instrumentation.h" #include "cc/test/fake_compositor_frame_reporting_controller.h" #include "testing/gtest/include/gtest/gtest.h" @@ -53,43 +52,6 @@ class CompositorTimingHistoryTest : public testing::Test { base::TimeTicks Now() { return now_; } - // TODO(xidachen): the composited_animations_count should just be 0. - void DrawMainFrame(int advance_ms, - int composited_animations_count, - int main_thread_animations_count, - bool current_frame_had_raf = false, - bool next_frame_has_pending_raf = false) { - timing_history_.WillBeginMainFrame(getFakeBeginFrameArg()); - timing_history_.BeginMainFrameStarted(Now()); - timing_history_.WillCommit(); - timing_history_.DidCommit(); - timing_history_.ReadyToActivate(); - timing_history_.WillActivate(); - timing_history_.DidActivate(); - timing_history_.WillDraw(); - AdvanceNowBy(base::TimeDelta::FromMicroseconds(advance_ms)); - timing_history_.DidDraw(true, composited_animations_count, - main_thread_animations_count, current_frame_had_raf, - next_frame_has_pending_raf, false); - } - - void DrawImplFrame(int advance_ms, - int composited_animations_count, - int main_thread_animations_count, - bool has_custom_property_animation) { - viz::BeginFrameArgs args_ = getFakeBeginFrameArg(); - timing_history_.WillBeginMainFrame(args_); - timing_history_.BeginMainFrameStarted(Now()); - timing_history_.BeginMainFrameAborted(args_.frame_id); - timing_history_.WillActivate(); - timing_history_.DidActivate(); - timing_history_.WillDraw(); - AdvanceNowBy(base::TimeDelta::FromMicroseconds(advance_ms)); - timing_history_.DidDraw(false, composited_animations_count, - main_thread_animations_count, false, false, - has_custom_property_animation); - } - protected: std::unique_ptr<RenderingStatsInstrumentation> rendering_stats_; std::unique_ptr<CompositorFrameReportingController> reporting_controller_; @@ -148,7 +110,7 @@ TEST_F(CompositorTimingHistoryTest, AllSequential_Commit) { AdvanceNowBy(one_second); timing_history_.WillDraw(); AdvanceNowBy(draw_duration); - timing_history_.DidDraw(true, 0, 0, false, false, false); + timing_history_.DidDraw(true, false); EXPECT_EQ(begin_main_frame_queue_duration, timing_history_.BeginMainFrameQueueDurationCriticalEstimate()); @@ -200,7 +162,7 @@ TEST_F(CompositorTimingHistoryTest, AllSequential_BeginMainFrameAborted) { AdvanceNowBy(one_second); timing_history_.WillDraw(); AdvanceNowBy(draw_duration); - timing_history_.DidDraw(false, 0, 0, false, false, false); + timing_history_.DidDraw(false, false); EXPECT_EQ(base::TimeDelta(), timing_history_.BeginMainFrameQueueDurationCriticalEstimate()); @@ -320,179 +282,5 @@ TEST_F(CompositorTimingHistoryTest, BeginMainFrames_NewCriticalSlower) { timing_history_.BeginMainFrameQueueDurationNotCriticalEstimate()); } -void TestAnimationUMA(const base::HistogramTester& histogram_tester, - base::HistogramBase::Count composited_animation_frames, - base::HistogramBase::Count main_thread_animation_frames) { - histogram_tester.ExpectTotalCount( - "Scheduling.Renderer.DrawIntervalWithCompositedAnimations2", - composited_animation_frames); - histogram_tester.ExpectTotalCount( - "Scheduling.Renderer.DrawIntervalWithMainThreadAnimations2", - main_thread_animation_frames); -} - -TEST_F(CompositorTimingHistoryTest, AnimationNotReported) { - base::HistogramTester histogram_tester; - - // Initial frame has no main-thread animations or rAF. - DrawMainFrame(123, 0, 0); - TestAnimationUMA(histogram_tester, 0, 0); - - // The next frame has one composited and one main thread animation running, - // but as the previous frame had no animation we shouldn't report anything. - DrawMainFrame(456, 1, 1); - TestAnimationUMA(histogram_tester, 0, 0); - - DrawMainFrame(123, 0, 0); - TestAnimationUMA(histogram_tester, 0, 0); - - // The next frame has just one main thread animation running, but again as the - // previous frame had no animation we shouldn't report anything. - DrawMainFrame(456, 0, 1); - TestAnimationUMA(histogram_tester, 0, 0); - - DrawMainFrame(123, 0, 0); - TestAnimationUMA(histogram_tester, 0, 0); - - // The next frame has no main thread animations but did have a rAF callback. - // Again as the previous frame had no visual change we shouldn't report. - DrawMainFrame(123, 0, 0, true); - TestAnimationUMA(histogram_tester, 0, 0); - - DrawMainFrame(123, 0, 0); - TestAnimationUMA(histogram_tester, 0, 0); - - // Finally, test the combination of both main thread animations and rAF - // callbacks being called. - DrawMainFrame(123, 1, 2, true); - TestAnimationUMA(histogram_tester, 0, 0); -} - -TEST_F(CompositorTimingHistoryTest, ConsecutiveFramesAnimationsReported) { - base::HistogramTester histogram_tester; - - DrawMainFrame(123, 1, 0); - TestAnimationUMA(histogram_tester, 0, 0); - - DrawMainFrame(456, 1, 0); - TestAnimationUMA(histogram_tester, 1, 0); - histogram_tester.ExpectBucketCount( - "Scheduling.Renderer.DrawIntervalWithCompositedAnimations2", 456, 1); - - DrawMainFrame(321, 0, 1); - TestAnimationUMA(histogram_tester, 1, 0); - - DrawMainFrame(654, 0, 1); - TestAnimationUMA(histogram_tester, 1, 1); - histogram_tester.ExpectBucketCount( - "Scheduling.Renderer.DrawIntervalWithMainThreadAnimations2", 654, 1); - - DrawMainFrame(123, 0, 1); - TestAnimationUMA(histogram_tester, 1, 2); - - DrawMainFrame(456, 0, 1); - TestAnimationUMA(histogram_tester, 1, 3); - - // Main thread and rAF animations are both considered to be part of the same - // animation type. - DrawMainFrame(789, 0, 0, true, true); - TestAnimationUMA(histogram_tester, 1, 4); - - DrawMainFrame(987, 0, 1, false); - TestAnimationUMA(histogram_tester, 1, 5); - - // However if there is no pending rAF for a frame, we don't count the one - // after it as being linked. - DrawMainFrame(789, 0, 0, true, false); - TestAnimationUMA(histogram_tester, 1, 6); - - DrawMainFrame(987, 0, 0, true, true); - TestAnimationUMA(histogram_tester, 1, 6); -} - -TEST_F(CompositorTimingHistoryTest, InterFrameAnimationsNotReported) { - base::HistogramTester histogram_tester; - - DrawMainFrame(123, 0, 1); - TestAnimationUMA(histogram_tester, 0, 0); - - // The previous frame had a main thread animation, where the current one is - // main thread compositable animation, we don't measure the timing from a - // different animation type. - DrawMainFrame(456, 0, 1); - TestAnimationUMA(histogram_tester, 0, 1); - - DrawMainFrame(321, 1, 0); - TestAnimationUMA(histogram_tester, 0, 1); - - DrawMainFrame(654, 0, 1); - TestAnimationUMA(histogram_tester, 0, 1); - - DrawMainFrame(123, 1, 0); - TestAnimationUMA(histogram_tester, 0, 1); -} - -TEST_F(CompositorTimingHistoryTest, AnimationsWithNewActiveTreeNotUsed) { - base::HistogramTester histogram_tester; - - DrawImplFrame(123, 1, 1, false); - TestAnimationUMA(histogram_tester, 0, 0); - - DrawImplFrame(456, 1, 0, false); - TestAnimationUMA(histogram_tester, 1, 0); - histogram_tester.ExpectBucketCount( - "Scheduling.Renderer.DrawIntervalWithCompositedAnimations2", 456, 1); - - DrawMainFrame(321, 0, 1); - TestAnimationUMA(histogram_tester, 1, 0); - - // This frame verifies that we record that there is a composited animation, - // so in the next frame when there is a composited animation, we report it. - DrawImplFrame(234, 1, 1, false); - TestAnimationUMA(histogram_tester, 1, 0); - - // Even though the previous frame had no main thread animation, we report it - // in this frame because the previous main frame had a main thread animation - // with the time between main frame draws. - DrawMainFrame(654, 1, 1); - TestAnimationUMA(histogram_tester, 2, 1); - histogram_tester.ExpectBucketCount( - "Scheduling.Renderer.DrawIntervalWithCompositedAnimations2", 654, 1); - // The recorded time for this main thread animation should be the total time - // between the two new tree activations, which is 234 + 654 = 888. - histogram_tester.ExpectBucketCount( - "Scheduling.Renderer.DrawIntervalWithMainThreadAnimations2", 888, 1); - - DrawImplFrame(123, 1, 0, false); - TestAnimationUMA(histogram_tester, 3, 1); - histogram_tester.ExpectBucketCount( - "Scheduling.Renderer.DrawIntervalWithCompositedAnimations2", 123, 1); -} - -TEST_F(CompositorTimingHistoryTest, CustomPropertyAnimations) { - base::HistogramTester histogram_tester; - - DrawImplFrame(123, 1, 0, true); - TestAnimationUMA(histogram_tester, 0, 0); - - DrawImplFrame(456, 1, 0, true); - TestAnimationUMA(histogram_tester, 1, 0); - - histogram_tester.ExpectBucketCount( - "Scheduling.Renderer.DrawIntervalWithCompositedAnimations2", 456, 1); - histogram_tester.ExpectBucketCount( - "Scheduling.Renderer.DrawIntervalWithCustomPropertyAnimations2", 456, 1); - - DrawImplFrame(1234, 1, 0, false); - DrawImplFrame(2345, 1, 0, true); - TestAnimationUMA(histogram_tester, 3, 0); - histogram_tester.ExpectBucketCount( - "Scheduling.Renderer.DrawIntervalWithCompositedAnimations2", 2345, 1); - // This impl frame does have custom property animation, but the previous impl - // frame doesn't, so we won't report it. - histogram_tester.ExpectBucketCount( - "Scheduling.Renderer.DrawIntervalWithCustomPropertyAnimations2", 2345, 0); -} - } // namespace } // namespace cc diff --git a/chromium/cc/metrics/dropped_frame_counter.cc b/chromium/cc/metrics/dropped_frame_counter.cc new file mode 100644 index 00000000000..56564a2415f --- /dev/null +++ b/chromium/cc/metrics/dropped_frame_counter.cc @@ -0,0 +1,45 @@ +// 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 "cc/metrics/dropped_frame_counter.h" + +#include <stddef.h> + +#include <limits> + +#include "base/memory/ptr_util.h" +#include "base/metrics/histogram_macros.h" + +namespace cc { + +DroppedFrameCounter::DroppedFrameCounter() = default; + +uint32_t DroppedFrameCounter::GetAverageThroughput() const { + size_t good_frames = 0; + for (auto it = --end(); it; --it) { + if (**it == kFrameStateComplete) + ++good_frames; + } + double throughput = 100. * good_frames / ring_buffer_.BufferSize(); + return static_cast<uint32_t>(throughput); +} + +void DroppedFrameCounter::AddGoodFrame() { + ring_buffer_.SaveToBuffer(kFrameStateComplete); + ++total_frames_; +} + +void DroppedFrameCounter::AddPartialFrame() { + ring_buffer_.SaveToBuffer(kFrameStatePartial); + ++total_frames_; + ++total_partial_; +} + +void DroppedFrameCounter::AddDroppedFrame() { + ring_buffer_.SaveToBuffer(kFrameStateDropped); + ++total_frames_; + ++total_dropped_; +} + +} // namespace cc diff --git a/chromium/cc/metrics/dropped_frame_counter.h b/chromium/cc/metrics/dropped_frame_counter.h new file mode 100644 index 00000000000..4929295ddce --- /dev/null +++ b/chromium/cc/metrics/dropped_frame_counter.h @@ -0,0 +1,55 @@ +// 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 CC_METRICS_DROPPED_FRAME_COUNTER_H_ +#define CC_METRICS_DROPPED_FRAME_COUNTER_H_ + +#include <stddef.h> + +#include <memory> + +#include "base/containers/ring_buffer.h" +#include "base/time/time.h" + +namespace cc { + +// This class maintains a counter for produced/dropped frames, and can be used +// to estimate the recent throughput. +class DroppedFrameCounter { + public: + enum FrameState { + kFrameStateDropped, + kFrameStatePartial, + kFrameStateComplete + }; + DroppedFrameCounter(); + + DroppedFrameCounter(const DroppedFrameCounter&) = delete; + DroppedFrameCounter& operator=(const DroppedFrameCounter&) = delete; + + size_t frame_history_size() const { return ring_buffer_.BufferSize(); } + size_t total_frames() const { return total_frames_; } + size_t total_compositor_dropped() const { return total_dropped_; } + size_t total_main_dropped() const { return total_partial_; } + + uint32_t GetAverageThroughput() const; + + typedef base::RingBuffer<FrameState, 180> RingBufferType; + RingBufferType::Iterator begin() const { return ring_buffer_.Begin(); } + RingBufferType::Iterator end() const { return ring_buffer_.End(); } + + void AddGoodFrame(); + void AddPartialFrame(); + void AddDroppedFrame(); + + private: + RingBufferType ring_buffer_; + size_t total_frames_ = 0; + size_t total_partial_ = 0; + size_t total_dropped_ = 0; +}; + +} // namespace cc + +#endif // CC_METRICS_DROPPED_FRAME_COUNTER_H_ diff --git a/chromium/cc/metrics/event_metrics.cc b/chromium/cc/metrics/event_metrics.cc index 326eaee70a1..951cdfc2ceb 100644 --- a/chromium/cc/metrics/event_metrics.cc +++ b/chromium/cc/metrics/event_metrics.cc @@ -4,98 +4,130 @@ #include "cc/metrics/event_metrics.h" +#include <utility> + #include "base/check.h" #include "base/memory/ptr_util.h" #include "base/notreached.h" #include "base/stl_util.h" namespace cc { +namespace { + +constexpr struct { + EventMetrics::EventType metrics_event_type; + ui::EventType ui_event_type; + const char* name; +} kWhitelistedEvents[] = { +#define EVENT_TYPE(name, ui_type) \ + { EventMetrics::EventType::k##name, ui_type, #name } + EVENT_TYPE(MousePressed, ui::ET_MOUSE_PRESSED), + EVENT_TYPE(MouseReleased, ui::ET_MOUSE_RELEASED), + EVENT_TYPE(MouseWheel, ui::ET_MOUSEWHEEL), + EVENT_TYPE(KeyPressed, ui::ET_KEY_PRESSED), + EVENT_TYPE(KeyReleased, ui::ET_KEY_RELEASED), + EVENT_TYPE(TouchPressed, ui::ET_TOUCH_PRESSED), + EVENT_TYPE(TouchReleased, ui::ET_TOUCH_RELEASED), + EVENT_TYPE(TouchMoved, ui::ET_TOUCH_MOVED), + EVENT_TYPE(GestureScrollBegin, ui::ET_GESTURE_SCROLL_BEGIN), + EVENT_TYPE(GestureScrollUpdate, ui::ET_GESTURE_SCROLL_UPDATE), + EVENT_TYPE(GestureScrollEnd, ui::ET_GESTURE_SCROLL_END), + EVENT_TYPE(GestureDoubleTap, ui::ET_GESTURE_DOUBLE_TAP), + EVENT_TYPE(GestureLongPress, ui::ET_GESTURE_LONG_PRESS), + EVENT_TYPE(GestureLongTap, ui::ET_GESTURE_LONG_TAP), + EVENT_TYPE(GestureShowPress, ui::ET_GESTURE_SHOW_PRESS), + EVENT_TYPE(GestureTap, ui::ET_GESTURE_TAP), + EVENT_TYPE(GestureTapCancel, ui::ET_GESTURE_TAP_CANCEL), + EVENT_TYPE(GestureTapDown, ui::ET_GESTURE_TAP_DOWN), + EVENT_TYPE(GestureTapUnconfirmed, ui::ET_GESTURE_TAP_UNCONFIRMED), + EVENT_TYPE(GestureTwoFingerTap, ui::ET_GESTURE_TWO_FINGER_TAP), +#undef EVENT_TYPE +}; +static_assert(base::size(kWhitelistedEvents) == + static_cast<int>(EventMetrics::EventType::kMaxValue) + 1, + "EventMetrics::EventType has changed."); + +constexpr struct { + EventMetrics::ScrollType metrics_scroll_type; + ui::ScrollInputType ui_scroll_type; + const char* name; +} kScrollTypes[] = { +#define SCROLL_TYPE(name, ui_type) \ + { EventMetrics::ScrollType::k##name, ui_type, #name } + SCROLL_TYPE(Autoscroll, ui::ScrollInputType::kAutoscroll), + SCROLL_TYPE(Scrollbar, ui::ScrollInputType::kScrollbar), + SCROLL_TYPE(Touchscreen, ui::ScrollInputType::kTouchscreen), + SCROLL_TYPE(Wheel, ui::ScrollInputType::kWheel), +#undef SCROLL_TYPE +}; +static_assert(base::size(kScrollTypes) == + static_cast<int>(EventMetrics::ScrollType::kMaxValue) + 1, + "EventMetrics::ScrollType has changed."); + +base::Optional<EventMetrics::EventType> ToWhitelistedEventType( + ui::EventType ui_event_type) { + for (size_t i = 0; i < base::size(kWhitelistedEvents); i++) { + if (ui_event_type == kWhitelistedEvents[i].ui_event_type) { + EventMetrics::EventType metrics_event_type = + static_cast<EventMetrics::EventType>(i); + DCHECK_EQ(metrics_event_type, kWhitelistedEvents[i].metrics_event_type); + return metrics_event_type; + } + } + return base::nullopt; +} + +base::Optional<EventMetrics::ScrollType> ToScrollType( + const base::Optional<ui::ScrollInputType>& scroll_input_type) { + if (!scroll_input_type) + return base::nullopt; + + for (size_t i = 0; i < base::size(kScrollTypes); i++) { + if (*scroll_input_type == kScrollTypes[i].ui_scroll_type) { + EventMetrics::ScrollType metrics_scroll_type = + static_cast<EventMetrics::ScrollType>(i); + DCHECK_EQ(metrics_scroll_type, kScrollTypes[i].metrics_scroll_type); + return metrics_scroll_type; + } + } + NOTREACHED(); + return base::nullopt; +} + +} // namespace std::unique_ptr<EventMetrics> EventMetrics::Create( ui::EventType type, base::TimeTicks time_stamp, base::Optional<ui::ScrollInputType> scroll_input_type) { - switch (type) { - case ui::ET_MOUSE_PRESSED: - case ui::ET_MOUSE_RELEASED: - case ui::ET_MOUSEWHEEL: - case ui::ET_KEY_PRESSED: - case ui::ET_KEY_RELEASED: - case ui::ET_TOUCH_PRESSED: - case ui::ET_TOUCH_RELEASED: - case ui::ET_TOUCH_MOVED: - case ui::ET_GESTURE_SCROLL_BEGIN: - case ui::ET_GESTURE_SCROLL_UPDATE: - case ui::ET_GESTURE_SCROLL_END: - return base::WrapUnique( - new EventMetrics(type, time_stamp, scroll_input_type)); - default: - return nullptr; - } + base::Optional<EventType> whitelisted_type = ToWhitelistedEventType(type); + if (!whitelisted_type) + return nullptr; + return base::WrapUnique(new EventMetrics(*whitelisted_type, time_stamp, + ToScrollType(scroll_input_type))); } -EventMetrics::EventMetrics( - ui::EventType type, - base::TimeTicks time_stamp, - base::Optional<ui::ScrollInputType> scroll_input_type) - : type_(type), - time_stamp_(time_stamp), - scroll_input_type_(scroll_input_type) {} +EventMetrics::EventMetrics(EventType type, + base::TimeTicks time_stamp, + base::Optional<ScrollType> scroll_type) + : type_(type), time_stamp_(time_stamp), scroll_type_(scroll_type) {} EventMetrics::EventMetrics(const EventMetrics&) = default; EventMetrics& EventMetrics::operator=(const EventMetrics&) = default; const char* EventMetrics::GetTypeName() const { - switch (type_) { - case ui::ET_MOUSE_PRESSED: - return "MousePressed"; - case ui::ET_MOUSE_RELEASED: - return "MouseReleased"; - case ui::ET_MOUSEWHEEL: - return "MouseWheel"; - case ui::ET_KEY_PRESSED: - // TODO(crbug/1071645): Currently, all ET_KEY_PRESSED events are reported - // under EventLatency.KeyPressed histogram. This includes both key-down - // and key-char events. Consider reporting them separately. - return "KeyPressed"; - case ui::ET_KEY_RELEASED: - return "KeyReleased"; - case ui::ET_TOUCH_PRESSED: - return "TouchPressed"; - case ui::ET_TOUCH_RELEASED: - return "TouchReleased"; - case ui::ET_TOUCH_MOVED: - return "TouchMoved"; - case ui::ET_GESTURE_SCROLL_BEGIN: - return "GestureScrollBegin"; - case ui::ET_GESTURE_SCROLL_UPDATE: - return "GestureScrollUpdate"; - case ui::ET_GESTURE_SCROLL_END: - return "GestureScrollEnd"; - default: - NOTREACHED(); - return nullptr; - } + return kWhitelistedEvents[static_cast<int>(type_)].name; } const char* EventMetrics::GetScrollTypeName() const { - DCHECK(scroll_input_type_) << "Event is not a scroll event"; - - switch (*scroll_input_type_) { - case ui::ScrollInputType::kTouchscreen: - return "Touchscreen"; - case ui::ScrollInputType::kWheel: - return "Wheel"; - case ui::ScrollInputType::kAutoscroll: - return "Autoscroll"; - case ui::ScrollInputType::kScrollbar: - return "Scrollbar"; - } + DCHECK(scroll_type_) << "Event is not a scroll event."; + + return kScrollTypes[static_cast<int>(*scroll_type_)].name; } bool EventMetrics::operator==(const EventMetrics& other) const { - return std::tie(type_, time_stamp_, scroll_input_type_) == - std::tie(other.type_, other.time_stamp_, other.scroll_input_type_); + return std::tie(type_, time_stamp_, scroll_type_) == + std::tie(other.type_, other.time_stamp_, other.scroll_type_); } // EventMetricsSet diff --git a/chromium/cc/metrics/event_metrics.h b/chromium/cc/metrics/event_metrics.h index 8de2fc58be9..df37365b7ed 100644 --- a/chromium/cc/metrics/event_metrics.h +++ b/chromium/cc/metrics/event_metrics.h @@ -6,6 +6,7 @@ #define CC_METRICS_EVENT_METRICS_H_ #include <memory> +#include <vector> #include "base/optional.h" #include "base/time/time.h" @@ -19,6 +20,45 @@ namespace cc { // latency metrics. class CC_EXPORT EventMetrics { public: + // Whitelisted event types. This list should be in the same order as values of + // EventLatencyEventType enum from enums.xml file. + enum class EventType { + kMousePressed, + kMouseReleased, + kMouseWheel, + // TODO(crbug/1071645): Currently, all ET_KEY_PRESSED events are reported + // under EventLatency.KeyPressed histogram. This includes both key-down and + // key-char events. Consider reporting them separately. + kKeyPressed, + kKeyReleased, + kTouchPressed, + kTouchReleased, + kTouchMoved, + kGestureScrollBegin, + kGestureScrollUpdate, + kGestureScrollEnd, + kGestureDoubleTap, + kGestureLongPress, + kGestureLongTap, + kGestureShowPress, + kGestureTap, + kGestureTapCancel, + kGestureTapDown, + kGestureTapUnconfirmed, + kGestureTwoFingerTap, + kMaxValue = kGestureTwoFingerTap, + }; + + // Type of scroll events. This list should be in the same order as values of + // EventLatencyScrollInputType enum from enums.xml file. + enum class ScrollType { + kAutoscroll, + kScrollbar, + kTouchscreen, + kWheel, + kMaxValue = kWheel, + }; + // Returns a new instance if |type| is a whitelisted event type. Otherwise, // returns nullptr. static std::unique_ptr<EventMetrics> Create( @@ -29,36 +69,33 @@ class CC_EXPORT EventMetrics { EventMetrics(const EventMetrics&); EventMetrics& operator=(const EventMetrics&); - // Returns a string representing event type. Should only be called for - // whitelisted event types. - const char* GetTypeName() const; - - // Returns a string representing scroll input type. Should only be called for - // scroll events. - const char* GetScrollTypeName() const; + EventType type() const { return type_; } - ui::EventType type() const { return type_; } + // Returns a string representing event type. + const char* GetTypeName() const; base::TimeTicks time_stamp() const { return time_stamp_; } - const base::Optional<ui::ScrollInputType>& scroll_input_type() const { - return scroll_input_type_; - } + const base::Optional<ScrollType>& scroll_type() const { return scroll_type_; } + + // Returns a string representing input type for a scroll event. Should only be + // called for scroll events. + const char* GetScrollTypeName() const; // Used in tests to check expectations on EventMetrics objects. bool operator==(const EventMetrics& other) const; private: - EventMetrics(ui::EventType type, + EventMetrics(EventType type, base::TimeTicks time_stamp, - base::Optional<ui::ScrollInputType> scroll_input_type); + base::Optional<ScrollType> scroll_type); - ui::EventType type_; + EventType type_; base::TimeTicks time_stamp_; // Only available for scroll events and represents the type of input device // for the event. - base::Optional<ui::ScrollInputType> scroll_input_type_; + base::Optional<ScrollType> scroll_type_; }; // Struct storing event metrics from both main and impl threads. diff --git a/chromium/cc/metrics/frame_sequence_metrics.cc b/chromium/cc/metrics/frame_sequence_metrics.cc index 0cefe55fd51..5933096b855 100644 --- a/chromium/cc/metrics/frame_sequence_metrics.cc +++ b/chromium/cc/metrics/frame_sequence_metrics.cc @@ -4,6 +4,10 @@ #include "cc/metrics/frame_sequence_metrics.h" +#include <memory> +#include <string> +#include <utility> + #include "base/metrics/histogram.h" #include "base/metrics/histogram_macros.h" #include "base/strings/strcat.h" @@ -91,13 +95,20 @@ bool IsInteractionType(FrameSequenceTrackerType sequence_type) { FrameSequenceMetrics::FrameSequenceMetrics(FrameSequenceTrackerType type, ThroughputUkmReporter* ukm_reporter) : type_(type), throughput_ukm_reporter_(ukm_reporter) { - TRACE_EVENT_NESTABLE_ASYNC_BEGIN1( - "cc,benchmark", "FrameSequenceTracker", TRACE_ID_LOCAL(this), "name", - FrameSequenceTracker::GetFrameSequenceTrackerTypeName(type_)); } -FrameSequenceMetrics::~FrameSequenceMetrics() { +FrameSequenceMetrics::~FrameSequenceMetrics() = default; + +void FrameSequenceMetrics::ReportLeftoverData() { if (HasDataLeftForReporting()) { + // Do this before ReportMetrics() which clears the throughput data. + // TODO(xidachen): Find a way to make ThroughputUkmReporter to directly talk + // to LayerTreeHostClient, and submit throughput data. Instead of storing + // the values in ThroughputUkmReporter. + if (type_ == FrameSequenceTrackerType::kUniversal && + HasEnoughDataForReporting()) { + throughput_ukm_reporter_->ComputeUniversalThroughput(this); + } ReportMetrics(); } } @@ -136,6 +147,8 @@ FrameSequenceMetrics::ThreadType FrameSequenceMetrics::GetEffectiveThread() return ThreadType::kSlower; case FrameSequenceTrackerType::kCustom: + return ThreadType::kMain; + case FrameSequenceTrackerType::kMaxType: NOTREACHED(); } @@ -144,6 +157,8 @@ FrameSequenceMetrics::ThreadType FrameSequenceMetrics::GetEffectiveThread() void FrameSequenceMetrics::Merge( std::unique_ptr<FrameSequenceMetrics> metrics) { + // Merging custom trackers are not supported. + DCHECK_NE(type_, FrameSequenceTrackerType::kCustom); DCHECK_EQ(type_, metrics->type_); DCHECK_EQ(GetEffectiveThread(), metrics->GetEffectiveThread()); impl_throughput_.Merge(metrics->impl_throughput_); @@ -169,6 +184,16 @@ bool FrameSequenceMetrics::HasDataLeftForReporting() const { main_throughput_.frames_expected > 0; } +void FrameSequenceMetrics::AdoptTrace(FrameSequenceMetrics* adopt_from) { + DCHECK(!trace_data_.trace_id); + trace_data_.trace_id = adopt_from->trace_data_.trace_id; + adopt_from->trace_data_.trace_id = nullptr; +} + +void FrameSequenceMetrics::AdvanceTrace(base::TimeTicks timestamp) { + trace_data_.Advance(timestamp); +} + void FrameSequenceMetrics::ComputeAggregatedThroughput() { // Whenever we are expecting and producing main frames, we are expecting and // producing impl frames as well. As an example, if we expect one main frame @@ -183,10 +208,11 @@ void FrameSequenceMetrics::ComputeAggregatedThroughput() { void FrameSequenceMetrics::ReportMetrics() { DCHECK_LE(impl_throughput_.frames_produced, impl_throughput_.frames_expected); DCHECK_LE(main_throughput_.frames_produced, main_throughput_.frames_expected); - TRACE_EVENT_NESTABLE_ASYNC_END2( - "cc,benchmark", "FrameSequenceTracker", TRACE_ID_LOCAL(this), "args", - ThroughputData::ToTracedValue(impl_throughput_, main_throughput_), - "checkerboard", frames_checkerboarded_); + DCHECK_LE(aggregated_throughput_.frames_produced, + aggregated_throughput_.frames_expected); + + // Terminates |trace_data_| for all types of FrameSequenceTracker. + trace_data_.Terminate(); if (type_ == FrameSequenceTrackerType::kCustom) { DCHECK(!custom_reporter_.is_null()); @@ -199,8 +225,6 @@ void FrameSequenceMetrics::ReportMetrics() { return; } - ComputeAggregatedThroughput(); - // Report the throughput metrics. base::Optional<int> impl_throughput_percent = ThroughputData::ReportHistogram( this, ThreadType::kCompositor, @@ -220,7 +244,8 @@ void FrameSequenceMetrics::ReportMetrics() { this, ThreadType::kSlower, GetIndexForMetric(FrameSequenceMetrics::ThreadType::kSlower, type_), aggregated_throughput_); - if (aggregated_throughput_percent.has_value() && throughput_ukm_reporter_) { + if (aggregated_throughput_percent.has_value() && throughput_ukm_reporter_ && + type_ != FrameSequenceTrackerType::kUniversal) { throughput_ukm_reporter_->ReportThroughputUkm( aggregated_throughput_percent, impl_throughput_percent, main_throughput_percent, type_); @@ -312,9 +337,7 @@ base::Optional<int> FrameSequenceMetrics::ThroughputData::ReportHistogram( // Throughput means the percent of frames that was expected to show on the // screen but didn't. In other words, the lower the throughput is, the // smoother user experience. - const int percent = - std::ceil(100 * (data.frames_expected - data.frames_produced) / - static_cast<double>(data.frames_expected)); + const int percent = data.DroppedFramePercent(); const bool is_animation = ShouldReportForAnimation(sequence_type, thread_type); @@ -323,6 +346,11 @@ base::Optional<int> FrameSequenceMetrics::ThroughputData::ReportHistogram( ThroughputUkmReporter* const ukm_reporter = metrics->ukm_reporter(); if (is_animation) { + TRACE_EVENT_INSTANT2("cc,benchmark", "PercentDroppedFrames.AllAnimations", + TRACE_EVENT_SCOPE_THREAD, "frames_expected", + data.frames_expected, "frames_produced", + data.frames_produced); + UMA_HISTOGRAM_PERCENTAGE( "Graphics.Smoothness.PercentDroppedFrames.AllAnimations", percent); if (ukm_reporter) { @@ -332,6 +360,10 @@ base::Optional<int> FrameSequenceMetrics::ThroughputData::ReportHistogram( } if (is_interaction) { + TRACE_EVENT_INSTANT2("cc,benchmark", "PercentDroppedFrames.AllInteractions", + TRACE_EVENT_SCOPE_THREAD, "frames_expected", + data.frames_expected, "frames_produced", + data.frames_produced); UMA_HISTOGRAM_PERCENTAGE( "Graphics.Smoothness.PercentDroppedFrames.AllInteractions", percent); if (ukm_reporter) { @@ -341,6 +373,10 @@ base::Optional<int> FrameSequenceMetrics::ThroughputData::ReportHistogram( } if (is_animation || is_interaction) { + TRACE_EVENT_INSTANT2("cc,benchmark", "PercentDroppedFrames.AllSequences", + TRACE_EVENT_SCOPE_THREAD, "frames_expected", + data.frames_expected, "frames_produced", + data.frames_produced); UMA_HISTOGRAM_PERCENTAGE( "Graphics.Smoothness.PercentDroppedFrames.AllSequences", percent); if (ukm_reporter) { @@ -368,4 +404,62 @@ base::Optional<int> FrameSequenceMetrics::ThroughputData::ReportHistogram( return percent; } +std::unique_ptr<base::trace_event::TracedValue> +FrameSequenceMetrics::ThroughputData::ToTracedValue( + const ThroughputData& impl, + const ThroughputData& main, + ThreadType effective_thread) { + auto dict = std::make_unique<base::trace_event::TracedValue>(); + if (effective_thread == ThreadType::kMain) { + dict->SetInteger("main-frames-produced", main.frames_produced); + dict->SetInteger("main-frames-expected", main.frames_expected); + } else { + dict->SetInteger("impl-frames-produced", impl.frames_produced); + dict->SetInteger("impl-frames-expected", impl.frames_expected); + } + return dict; +} + +FrameSequenceMetrics::TraceData::TraceData(FrameSequenceMetrics* m) + : metrics(m) { + TRACE_EVENT_CATEGORY_GROUP_ENABLED("cc,benchmark", &enabled); +} + +FrameSequenceMetrics::TraceData::~TraceData() = default; + +void FrameSequenceMetrics::TraceData::Terminate() { + if (!enabled || !trace_id) + return; + TRACE_EVENT_NESTABLE_ASYNC_END2( + "cc,benchmark", "FrameSequenceTracker", TRACE_ID_LOCAL(trace_id), "args", + ThroughputData::ToTracedValue(metrics->impl_throughput(), + metrics->main_throughput(), + metrics->GetEffectiveThread()), + "checkerboard", metrics->frames_checkerboarded()); + trace_id = nullptr; +} + +void FrameSequenceMetrics::TraceData::Advance(base::TimeTicks new_timestamp) { + if (!enabled) + return; + if (!trace_id) { + trace_id = this; + TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP1( + "cc,benchmark", "FrameSequenceTracker", TRACE_ID_LOCAL(trace_id), + this->last_timestamp, "name", + FrameSequenceTracker::GetFrameSequenceTrackerTypeName(metrics->type())); + } + // Use different names, because otherwise the trace-viewer shows the slices in + // the same color, and that makes it difficult to tell the traces apart from + // each other. + const char* trace_names[] = {"Frame", "Frame ", "Frame "}; + TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0( + "cc,benchmark", trace_names[++this->frame_count % 3], + TRACE_ID_LOCAL(trace_id), this->last_timestamp); + TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0( + "cc,benchmark", trace_names[this->frame_count % 3], + TRACE_ID_LOCAL(trace_id), new_timestamp); + this->last_timestamp = new_timestamp; +} + } // namespace cc diff --git a/chromium/cc/metrics/frame_sequence_metrics.h b/chromium/cc/metrics/frame_sequence_metrics.h index 0417899c3a9..3a77fc57775 100644 --- a/chromium/cc/metrics/frame_sequence_metrics.h +++ b/chromium/cc/metrics/frame_sequence_metrics.h @@ -5,6 +5,8 @@ #ifndef CC_METRICS_FRAME_SEQUENCE_METRICS_H_ #define CC_METRICS_FRAME_SEQUENCE_METRICS_H_ +#include <memory> + #include "base/callback.h" #include "base/optional.h" #include "base/trace_event/traced_value.h" @@ -44,7 +46,8 @@ class CC_EXPORT FrameSequenceMetrics { struct ThroughputData { static std::unique_ptr<base::trace_event::TracedValue> ToTracedValue( const ThroughputData& impl, - const ThroughputData& main); + const ThroughputData& main, + ThreadType effective_thred); // Returns the throughput in percent, a return value of base::nullopt // indicates that no throughput metric is reported. @@ -62,6 +65,13 @@ class CC_EXPORT FrameSequenceMetrics { #endif } + int DroppedFramePercent() const { + if (frames_expected == 0) + return 0; + return std::ceil(100 * (frames_expected - frames_produced) / + static_cast<double>(frames_expected)); + } + // Tracks the number of frames that were expected to be shown during this // frame-sequence. uint32_t frames_expected = 0; @@ -113,10 +123,31 @@ class CC_EXPORT FrameSequenceMetrics { return throughput_ukm_reporter_; } + // Must be called before destructor. + void ReportLeftoverData(); + + void AdoptTrace(FrameSequenceMetrics* adopt_from); + void AdvanceTrace(base::TimeTicks timestamp); + private: void ComputeAggregatedThroughput(); + const FrameSequenceTrackerType type_; + // Tracks some data to generate useful trace events. + struct TraceData { + explicit TraceData(FrameSequenceMetrics* metrics); + ~TraceData(); + FrameSequenceMetrics* metrics; + base::TimeTicks last_timestamp = base::TimeTicks::Now(); + int frame_count = 0; + bool enabled = false; + void* trace_id = nullptr; + + void Advance(base::TimeTicks new_timestamp); + void Terminate(); + } trace_data_{this}; + // Pointer to the reporter owned by the FrameSequenceTrackerCollection. ThroughputUkmReporter* const throughput_ukm_reporter_; diff --git a/chromium/cc/metrics/frame_sequence_metrics_unittest.cc b/chromium/cc/metrics/frame_sequence_metrics_unittest.cc index ae43da19a24..c2a1f76babb 100644 --- a/chromium/cc/metrics/frame_sequence_metrics_unittest.cc +++ b/chromium/cc/metrics/frame_sequence_metrics_unittest.cc @@ -6,6 +6,9 @@ #include "base/macros.h" #include "base/test/metrics/histogram_tester.h" +#include "cc/metrics/throughput_ukm_reporter.h" +#include "cc/trees/ukm_manager.h" +#include "components/ukm/test_ukm_recorder.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -28,6 +31,7 @@ TEST(FrameSequenceMetricsTest, AggregatedThroughputClearedAfterReport) { nullptr); first.impl_throughput().frames_expected = 200u; first.impl_throughput().frames_produced = 190u; + first.aggregated_throughput().frames_expected = 170u; first.aggregated_throughput().frames_produced = 150u; first.ReportMetrics(); @@ -35,6 +39,28 @@ TEST(FrameSequenceMetricsTest, AggregatedThroughputClearedAfterReport) { EXPECT_EQ(first.aggregated_throughput().frames_produced, 0u); } +// Test that ThroughputUkmReporter::ReportThroughputUkm isn't called for the +// kUniversal tracker. +TEST(FrameSequenceMetricsTest, UniversalNotReportUkmAtRenderer) { + auto recorder = std::make_unique<ukm::TestUkmRecorder>(); + auto ukm_manager = std::make_unique<UkmManager>(std::move(recorder)); + ThroughputUkmReporter reporter(ukm_manager.get()); + auto metric = std::make_unique<FrameSequenceMetrics>( + FrameSequenceTrackerType::kUniversal, &reporter); + + metric->impl_throughput().frames_expected = 200u; + metric->impl_throughput().frames_produced = 190u; + metric->aggregated_throughput().frames_expected = 170u; + metric->aggregated_throughput().frames_produced = 150u; + metric->ReportMetrics(); + + // The corresponding |samples_to_next_event_| element is 0 if the + // ReportThroughputUkm isn't called. + EXPECT_EQ(reporter.GetSamplesToNextEventForTesting( + static_cast<int>(FrameSequenceTrackerType::kUniversal)), + 1u); +} + TEST(FrameSequenceMetricsTest, MergeMetrics) { // Create a metric with only a small number of frames. It shouldn't report any // metrics. diff --git a/chromium/cc/metrics/frame_sequence_tracker.cc b/chromium/cc/metrics/frame_sequence_tracker.cc index f1a1fd9b995..dec83686b65 100644 --- a/chromium/cc/metrics/frame_sequence_tracker.cc +++ b/chromium/cc/metrics/frame_sequence_tracker.cc @@ -4,7 +4,13 @@ #include "cc/metrics/frame_sequence_tracker.h" +#include <algorithm> +#include <memory> +#include <string> +#include <utility> + #include "base/bind.h" +#include "base/logging.h" #include "base/metrics/histogram.h" #include "base/metrics/histogram_macros.h" #include "base/trace_event/trace_event.h" @@ -27,6 +33,8 @@ namespace cc { +using ThreadType = FrameSequenceMetrics::ThreadType; + // In the |TRACKER_TRACE_STREAM|, we mod the numbers such as frame sequence // number, or frame token, such that the debug string is not too long. constexpr int kDebugStrMod = 1000; @@ -63,9 +71,9 @@ FrameSequenceTracker::FrameSequenceTracker( FrameSequenceTrackerType type, ThroughputUkmReporter* throughput_ukm_reporter) : custom_sequence_id_(-1), - metrics_(std::make_unique<FrameSequenceMetrics>(type, - throughput_ukm_reporter)), - trace_data_(metrics_.get()) { + metrics_( + std::make_unique<FrameSequenceMetrics>(type, + throughput_ukm_reporter)) { DCHECK_LT(type, FrameSequenceTrackerType::kMaxType); DCHECK(type != FrameSequenceTrackerType::kCustom); } @@ -76,13 +84,14 @@ FrameSequenceTracker::FrameSequenceTracker( : custom_sequence_id_(custom_sequence_id), metrics_(std::make_unique<FrameSequenceMetrics>( FrameSequenceTrackerType::kCustom, - /*ukm_reporter=*/nullptr)), - trace_data_(metrics_.get()) { + /*ukm_reporter=*/nullptr)) { DCHECK_GT(custom_sequence_id_, 0); metrics_->SetCustomReporter(std::move(custom_reporter)); } -FrameSequenceTracker::~FrameSequenceTracker() = default; +FrameSequenceTracker::~FrameSequenceTracker() { + CleanUp(); +} void FrameSequenceTracker::ScheduleTerminate() { // If the last frame has ended and there is no frame awaiting presentation, @@ -126,6 +135,8 @@ void FrameSequenceTracker::ReportBeginImplFrame( args.frame_id.sequence_number); impl_throughput().frames_expected += begin_impl_frame_data_.previous_sequence_delta; + aggregated_throughput().frames_expected += + begin_impl_frame_data_.previous_sequence_delta; #if DCHECK_IS_ON() ++impl_throughput().frames_received; #endif @@ -363,6 +374,8 @@ void FrameSequenceTracker::ReportFrameEnd( NOTREACHED() << TRACKER_DCHECK_MSG; #endif begin_impl_frame_data_.previous_sequence = 0; + if (!IsExpectingMainFrame()) + --aggregated_throughput().frames_expected; } // last_submitted_frame_ == 0 means the last impl frame has been presented. if (termination_status_ == TerminationStatus::kScheduledForTermination && @@ -423,15 +436,17 @@ void FrameSequenceTracker::ReportFramePresented( uint32_t impl_frames_produced = 0; uint32_t main_frames_produced = 0; - trace_data_.Advance(feedback.timestamp); - const bool was_presented = !feedback.timestamp.is_null(); + const bool was_presented = !feedback.failed(); if (was_presented && submitted_frame_since_last_presentation) { DCHECK_LT(impl_throughput().frames_produced, impl_throughput().frames_expected) << TRACKER_DCHECK_MSG; ++impl_throughput().frames_produced; ++impl_frames_produced; + if (metrics()->GetEffectiveThread() == ThreadType::kCompositor) { + metrics()->AdvanceTrace(feedback.timestamp); + } } if (was_presented) { @@ -448,6 +463,9 @@ void FrameSequenceTracker::ReportFramePresented( << TRACKER_DCHECK_MSG; ++main_throughput().frames_produced; ++main_frames_produced; + if (metrics()->GetEffectiveThread() == ThreadType::kMain) { + metrics()->AdvanceTrace(feedback.timestamp); + } } if (impl_frames_produced > 0) { @@ -647,16 +665,11 @@ bool FrameSequenceTracker::ShouldIgnoreSequence( return sequence_number != begin_impl_frame_data_.previous_sequence; } -std::unique_ptr<base::trace_event::TracedValue> -FrameSequenceMetrics::ThroughputData::ToTracedValue( - const ThroughputData& impl, - const ThroughputData& main) { - auto dict = std::make_unique<base::trace_event::TracedValue>(); - dict->SetInteger("impl-frames-produced", impl.frames_produced); - dict->SetInteger("impl-frames-expected", impl.frames_expected); - dict->SetInteger("main-frames-produced", main.frames_produced); - dict->SetInteger("main-frames-expected", main.frames_expected); - return dict; +bool FrameSequenceTracker::IsExpectingMainFrame() const { + bool last_main_not_processed = + begin_main_frame_data_.previous_sequence != 0 && + begin_main_frame_data_.previous_sequence != last_processed_main_sequence_; + return !main_frames_.empty() || last_main_not_processed; } bool FrameSequenceTracker::ShouldReportMetricsNow( @@ -675,22 +688,12 @@ std::unique_ptr<FrameSequenceMetrics> FrameSequenceTracker::TakeMetrics() { return std::move(metrics_); } +void FrameSequenceTracker::CleanUp() { + if (metrics_) + metrics_->ReportLeftoverData(); +} + FrameSequenceTracker::CheckerboardingData::CheckerboardingData() = default; FrameSequenceTracker::CheckerboardingData::~CheckerboardingData() = default; -FrameSequenceTracker::TraceData::TraceData(const void* id) : trace_id(id) {} -void FrameSequenceTracker::TraceData::Advance(base::TimeTicks new_timestamp) { - // Use different names, because otherwise the trace-viewer shows the slices in - // the same color, and that makes it difficult to tell the traces apart from - // each other. - const char* trace_names[] = {"Frame", "Frame ", "Frame "}; - TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0( - "cc,benchmark", trace_names[++this->frame_count % 3], - TRACE_ID_LOCAL(this->trace_id), this->last_timestamp); - TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0( - "cc,benchmark", trace_names[this->frame_count % 3], - TRACE_ID_LOCAL(this->trace_id), new_timestamp); - this->last_timestamp = new_timestamp; -} - } // namespace cc diff --git a/chromium/cc/metrics/frame_sequence_tracker.h b/chromium/cc/metrics/frame_sequence_tracker.h index 680b2a85ad9..86deb91b3bd 100644 --- a/chromium/cc/metrics/frame_sequence_tracker.h +++ b/chromium/cc/metrics/frame_sequence_tracker.h @@ -5,6 +5,9 @@ #ifndef CC_METRICS_FRAME_SEQUENCE_TRACKER_H_ #define CC_METRICS_FRAME_SEQUENCE_TRACKER_H_ +#include <memory> +#include <sstream> + #include "base/containers/circular_deque.h" #include "base/containers/flat_map.h" #include "base/containers/flat_set.h" @@ -101,6 +104,10 @@ class CC_EXPORT FrameSequenceTracker { std::unique_ptr<FrameSequenceMetrics> TakeMetrics(); + // Called by the destructor of FrameSequenceTrackerCollection, asking its + // |metrics_| to report. + void CleanUp(); + private: friend class FrameSequenceTrackerCollection; friend class FrameSequenceTrackerTest; @@ -158,6 +165,8 @@ class CC_EXPORT FrameSequenceTracker { bool ShouldIgnoreSequence(uint64_t sequence_number) const; + bool IsExpectingMainFrame() const; + const int custom_sequence_id_; TerminationStatus termination_status_ = TerminationStatus::kActive; @@ -247,16 +256,6 @@ class CC_EXPORT FrameSequenceTracker { // TODO(xidachen): remove this one. uint64_t current_begin_main_sequence_ = 0; - // Tracks some data to generate useful trace events. - struct TraceData { - explicit TraceData(const void* trace_id); - const void* trace_id; - base::TimeTicks last_timestamp = base::TimeTicks::Now(); - int frame_count = 0; - - void Advance(base::TimeTicks new_timestamp); - } trace_data_; - // True when an impl-impl is not ended. A tracker is ready for termination // only when the last impl-frame is ended (ReportFrameEnd). bool is_inside_frame_ = false; diff --git a/chromium/cc/metrics/frame_sequence_tracker_collection.cc b/chromium/cc/metrics/frame_sequence_tracker_collection.cc index 8dd78ca0ccd..1efd3a37159 100644 --- a/chromium/cc/metrics/frame_sequence_tracker_collection.cc +++ b/chromium/cc/metrics/frame_sequence_tracker_collection.cc @@ -11,6 +11,18 @@ namespace cc { +namespace { + +using ThreadType = FrameSequenceMetrics::ThreadType; + +bool IsScrollType(FrameSequenceTrackerType type) { + return type == FrameSequenceTrackerType::kTouchScroll || + type == FrameSequenceTrackerType::kWheelScroll || + type == FrameSequenceTrackerType::kScrollbarScroll; +} + +} // namespace + FrameSequenceTrackerCollection::FrameSequenceTrackerCollection( bool is_single_threaded, CompositorFrameReportingController* compositor_frame_reporting_controller) @@ -19,42 +31,86 @@ FrameSequenceTrackerCollection::FrameSequenceTrackerCollection( compositor_frame_reporting_controller) {} FrameSequenceTrackerCollection::~FrameSequenceTrackerCollection() { + CleanUp(); frame_trackers_.clear(); removal_trackers_.clear(); } -FrameSequenceMetrics* FrameSequenceTrackerCollection::StartSequence( - FrameSequenceTrackerType type) { +FrameSequenceTracker* FrameSequenceTrackerCollection::StartSequenceInternal( + FrameSequenceTrackerType type, + FrameSequenceMetrics::ThreadType scrolling_thread) { DCHECK_NE(FrameSequenceTrackerType::kCustom, type); - if (is_single_threaded_) return nullptr; - if (frame_trackers_.contains(type)) - return frame_trackers_[type]->metrics(); + auto key = std::make_pair(type, scrolling_thread); + if (frame_trackers_.contains(key)) + return frame_trackers_[key].get(); + auto tracker = base::WrapUnique( new FrameSequenceTracker(type, throughput_ukm_reporter_.get())); - frame_trackers_[type] = std::move(tracker); + frame_trackers_[key] = std::move(tracker); if (compositor_frame_reporting_controller_) compositor_frame_reporting_controller_->AddActiveTracker(type); - return frame_trackers_[type]->metrics(); + + auto* metrics = frame_trackers_[key]->metrics(); + if (accumulated_metrics_.contains(key)) { + metrics->AdoptTrace(accumulated_metrics_[key].get()); + } + if (IsScrollType(type)) { + DCHECK_NE(scrolling_thread, ThreadType::kUnknown); + metrics->SetScrollingThread(scrolling_thread); + } + return frame_trackers_[key].get(); +} + +FrameSequenceTracker* FrameSequenceTrackerCollection::StartSequence( + FrameSequenceTrackerType type) { + DCHECK(!IsScrollType(type)); + return StartSequenceInternal(type, ThreadType::kUnknown); +} + +FrameSequenceTracker* FrameSequenceTrackerCollection::StartScrollSequence( + FrameSequenceTrackerType type, + FrameSequenceMetrics::ThreadType scrolling_thread) { + DCHECK(IsScrollType(type)); + return StartSequenceInternal(type, scrolling_thread); +} + +void FrameSequenceTrackerCollection::CleanUp() { + for (auto& tracker : frame_trackers_) + tracker.second->CleanUp(); + for (auto& tracker : custom_frame_trackers_) + tracker.second->CleanUp(); + for (auto& tracker : removal_trackers_) + tracker->CleanUp(); + for (auto& metric : accumulated_metrics_) + metric.second->ReportLeftoverData(); + throughput_ukm_reporter_ = nullptr; } void FrameSequenceTrackerCollection::StopSequence( FrameSequenceTrackerType type) { DCHECK_NE(FrameSequenceTrackerType::kCustom, type); - if (!frame_trackers_.contains(type)) + auto key = std::make_pair(type, ThreadType::kUnknown); + if (IsScrollType(type)) { + key = std::make_pair(type, ThreadType::kCompositor); + if (!frame_trackers_.contains(key)) + key = std::make_pair(type, ThreadType::kMain); + } + + if (!frame_trackers_.contains(key)) return; std::unique_ptr<FrameSequenceTracker> tracker = - std::move(frame_trackers_[type]); + std::move(frame_trackers_[key]); if (compositor_frame_reporting_controller_) compositor_frame_reporting_controller_->RemoveActiveTracker( tracker->type()); - frame_trackers_.erase(type); + frame_trackers_.erase(key); tracker->ScheduleTerminate(); removal_trackers_.push_back(std::move(tracker)); DestroyTrackers(); @@ -193,10 +249,42 @@ void FrameSequenceTrackerCollection::NotifyFramePresented( tracker.second->ReportFramePresented(frame_token, feedback); for (auto& tracker : custom_frame_trackers_) tracker.second->ReportFramePresented(frame_token, feedback); - for (auto& tracker : removal_trackers_) tracker->ReportFramePresented(frame_token, feedback); + DestroyTrackers(); +} + +bool FrameSequenceTrackerCollection::HasThroughputData() const { + return throughput_ukm_reporter_ && + throughput_ukm_reporter_->HasThroughputData(); +} + +int FrameSequenceTrackerCollection::TakeLastAggregatedPercent() { + DCHECK(throughput_ukm_reporter_); + return throughput_ukm_reporter_->TakeLastAggregatedPercent(); +} + +int FrameSequenceTrackerCollection::TakeLastImplPercent() { + DCHECK(throughput_ukm_reporter_); + return throughput_ukm_reporter_->TakeLastImplPercent(); +} + +base::Optional<int> FrameSequenceTrackerCollection::TakeLastMainPercent() { + DCHECK(throughput_ukm_reporter_); + return throughput_ukm_reporter_->TakeLastMainPercent(); +} + +void FrameSequenceTrackerCollection::ComputeUniversalThroughputForTesting() { + DCHECK(throughput_ukm_reporter_); + const auto type = FrameSequenceTrackerType::kUniversal; + auto key = std::make_pair(type, ThreadType::kUnknown); + DCHECK(frame_trackers_.contains(key)); + throughput_ukm_reporter_->ComputeUniversalThroughput( + frame_trackers_[key]->metrics()); +} + +void FrameSequenceTrackerCollection::DestroyTrackers() { for (auto& tracker : removal_trackers_) { if (tracker->termination_status() == FrameSequenceTracker::TerminationStatus::kReadyForTermination) { @@ -209,9 +297,10 @@ void FrameSequenceTrackerCollection::NotifyFramePresented( // For kCustom typed trackers, |metrics| invokes AddCustomTrackerResult // on its destruction, which add its data to |custom_tracker_results_| // to be picked up by caller. - auto metrics = tracker->TakeMetrics(); - if (metrics->type() == FrameSequenceTrackerType::kCustom) + if (tracker->metrics() && + tracker->type() == FrameSequenceTrackerType::kCustom) continue; + auto metrics = tracker->TakeMetrics(); auto key = std::make_pair(metrics->type(), metrics->GetEffectiveThread()); if (accumulated_metrics_.contains(key)) { @@ -220,24 +309,17 @@ void FrameSequenceTrackerCollection::NotifyFramePresented( } if (metrics->HasEnoughDataForReporting()) { - if (metrics->type() == FrameSequenceTrackerType::kUniversal) { - uint32_t frames_expected = metrics->impl_throughput().frames_expected; - uint32_t frames_produced = - metrics->aggregated_throughput().frames_produced; - current_universal_throughput_ = std::floor( - 100 * frames_produced / static_cast<float>(frames_expected)); - } + // Do this before ReportMetrics() which clears the throughput data. + if (metrics->type() == FrameSequenceTrackerType::kUniversal) + throughput_ukm_reporter_->ComputeUniversalThroughput(metrics.get()); metrics->ReportMetrics(); } - if (metrics->HasDataLeftForReporting()) + if (metrics->HasDataLeftForReporting()) { accumulated_metrics_[key] = std::move(metrics); + } } } - DestroyTrackers(); -} - -void FrameSequenceTrackerCollection::DestroyTrackers() { base::EraseIf( removal_trackers_, [](const std::unique_ptr<FrameSequenceTracker>& tracker) { @@ -248,28 +330,40 @@ void FrameSequenceTrackerCollection::DestroyTrackers() { void FrameSequenceTrackerCollection::RecreateTrackers( const viz::BeginFrameArgs& args) { - std::vector<FrameSequenceTrackerType> recreate_trackers; + std::vector<std::pair<FrameSequenceTrackerType, ThreadType>> + recreate_trackers; for (const auto& tracker : frame_trackers_) { if (tracker.second->ShouldReportMetricsNow(args)) recreate_trackers.push_back(tracker.first); } - for (const auto& tracker_type : recreate_trackers) { + for (const auto& key : recreate_trackers) { + DCHECK(frame_trackers_[key]); + auto tracker_type = key.first; + ThreadType thread_type = key.second; + // StopSequence put the tracker in the |removal_trackers_|, which will // report its throughput data when its frame is presented. StopSequence(tracker_type); + // The frame sequence is still active, so create a new tracker to keep // tracking this sequence. - StartSequence(tracker_type); + if (thread_type != FrameSequenceMetrics::ThreadType::kUnknown) { + DCHECK(IsScrollType(tracker_type)); + StartScrollSequence(tracker_type, thread_type); + } else { + StartSequence(tracker_type); + } } } ActiveFrameSequenceTrackers FrameSequenceTrackerCollection::FrameSequenceTrackerActiveTypes() { ActiveFrameSequenceTrackers encoded_types = 0; - for (const auto& tracker : frame_trackers_) { + for (const auto& key : frame_trackers_) { + auto thread_type = key.first.first; encoded_types |= static_cast<ActiveFrameSequenceTrackers>( - 1 << static_cast<unsigned>(tracker.first)); + 1 << static_cast<unsigned>(thread_type)); } return encoded_types; } @@ -281,13 +375,6 @@ FrameSequenceTrackerCollection::TakeCustomTrackerResults() { return results; } -FrameSequenceTracker* FrameSequenceTrackerCollection::GetTrackerForTesting( - FrameSequenceTrackerType type) { - if (!frame_trackers_.contains(type)) - return nullptr; - return frame_trackers_[type].get(); -} - FrameSequenceTracker* FrameSequenceTrackerCollection::GetRemovalTrackerForTesting( FrameSequenceTrackerType type) { diff --git a/chromium/cc/metrics/frame_sequence_tracker_collection.h b/chromium/cc/metrics/frame_sequence_tracker_collection.h index 9eaf1efb6b2..0fecfce7895 100644 --- a/chromium/cc/metrics/frame_sequence_tracker_collection.h +++ b/chromium/cc/metrics/frame_sequence_tracker_collection.h @@ -6,6 +6,8 @@ #define CC_METRICS_FRAME_SEQUENCE_TRACKER_COLLECTION_H_ #include <memory> +#include <utility> +#include <vector> #include "base/containers/flat_map.h" #include "base/optional.h" @@ -47,8 +49,12 @@ class CC_EXPORT FrameSequenceTrackerCollection { FrameSequenceTrackerCollection& operator=( const FrameSequenceTrackerCollection&) = delete; - // Creates a tracker for the specified sequence-type. - FrameSequenceMetrics* StartSequence(FrameSequenceTrackerType type); + // Creates a new tracker for the specified sequence-type if one doesn't + // already exist. Returns the associated FrameSequenceTracker instance. + FrameSequenceTracker* StartSequence(FrameSequenceTrackerType type); + FrameSequenceTracker* StartScrollSequence( + FrameSequenceTrackerType type, + FrameSequenceMetrics::ThreadType scrolling_thread); // Schedules |tracker| for destruction. This is preferred instead of outright // desrtruction of the tracker, since this ensures that the actual tracker @@ -103,23 +109,35 @@ class CC_EXPORT FrameSequenceTrackerCollection { // Reports the accumulated kCustom tracker results and clears it. CustomTrackerResults TakeCustomTrackerResults(); - FrameSequenceTracker* GetTrackerForTesting(FrameSequenceTrackerType type); FrameSequenceTracker* GetRemovalTrackerForTesting( FrameSequenceTrackerType type); void SetUkmManager(UkmManager* manager); - base::Optional<int> current_universal_throughput() { - return current_universal_throughput_; - } + // These methods directly calls corresponding APIs in ThroughputUkmReporter, + // please refer to the ThroughputUkmReporter for details. + bool HasThroughputData() const; + int TakeLastAggregatedPercent(); + int TakeLastImplPercent(); + base::Optional<int> TakeLastMainPercent(); + + void ComputeUniversalThroughputForTesting(); private: friend class FrameSequenceTrackerTest; + FrameSequenceTracker* StartSequenceInternal( + FrameSequenceTrackerType type, + FrameSequenceMetrics::ThreadType scrolling_thread); + void RecreateTrackers(const viz::BeginFrameArgs& args); // Destroy the trackers that are ready to be terminated. void DestroyTrackers(); + // Ask all trackers to report their metrics if there is any, must be the first + // thing in the destructor. + void CleanUp(); + // Adds collected metrics data for |custom_sequence_id| to be picked up via // TakeCustomTrackerResults() below. void AddCustomTrackerResult( @@ -127,9 +145,19 @@ class CC_EXPORT FrameSequenceTrackerCollection { FrameSequenceMetrics::ThroughputData throughput_data); const bool is_single_threaded_; + // The reporter takes throughput data and connect to UkmManager to report it. + // Note: this has to be before the frame_trackers_. The reason is that a + // FrameSequenceTracker owners a FrameSequenceMetrics, so the destructor of + // the former calls the destructor of the later. FrameSequenceMetrics's + // destructor calls its ReportMetrics() which requires + // |throughput_ukm_reporter_| to be alive. So putting it before + // |frame_trackers_| to ensure that it is destroyed after the tracker. + std::unique_ptr<ThroughputUkmReporter> throughput_ukm_reporter_; + // The callsite can use the type to manipulate the tracker. - base::flat_map<FrameSequenceTrackerType, - std::unique_ptr<FrameSequenceTracker>> + base::flat_map< + std::pair<FrameSequenceTrackerType, FrameSequenceMetrics::ThreadType>, + std::unique_ptr<FrameSequenceTracker>> frame_trackers_; // Custom trackers are keyed by a custom sequence id. @@ -141,14 +169,10 @@ class CC_EXPORT FrameSequenceTrackerCollection { CompositorFrameReportingController* const compositor_frame_reporting_controller_; - // The reporter takes throughput data and connect to UkmManager to report it. - std::unique_ptr<ThroughputUkmReporter> throughput_ukm_reporter_; - base::flat_map< std::pair<FrameSequenceTrackerType, FrameSequenceMetrics::ThreadType>, std::unique_ptr<FrameSequenceMetrics>> accumulated_metrics_; - base::Optional<int> current_universal_throughput_; }; } // namespace cc diff --git a/chromium/cc/metrics/frame_sequence_tracker_unittest.cc b/chromium/cc/metrics/frame_sequence_tracker_unittest.cc index 2a6b8cd2b82..963cf1045f4 100644 --- a/chromium/cc/metrics/frame_sequence_tracker_unittest.cc +++ b/chromium/cc/metrics/frame_sequence_tracker_unittest.cc @@ -8,6 +8,9 @@ #include "base/test/metrics/histogram_tester.h" #include "cc/metrics/compositor_frame_reporting_controller.h" #include "cc/metrics/frame_sequence_tracker_collection.h" +#include "cc/metrics/throughput_ukm_reporter.h" +#include "cc/trees/ukm_manager.h" +#include "components/ukm/test_ukm_recorder.h" #include "components/viz/common/frame_sinks/begin_frame_args.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -40,16 +43,16 @@ class FrameSequenceTrackerTest : public testing::Test { /*should_report_metrics=*/true)), collection_(/*is_single_threaded=*/false, compositor_frame_reporting_controller_.get()) { - collection_.StartSequence(FrameSequenceTrackerType::kTouchScroll); - tracker_ = collection_.GetTrackerForTesting( - FrameSequenceTrackerType::kTouchScroll); + tracker_ = collection_.StartScrollSequence( + FrameSequenceTrackerType::kTouchScroll, + FrameSequenceMetrics::ThreadType::kCompositor); } ~FrameSequenceTrackerTest() override = default; - void CreateNewTracker() { - collection_.StartSequence(FrameSequenceTrackerType::kTouchScroll); - tracker_ = collection_.GetTrackerForTesting( - FrameSequenceTrackerType::kTouchScroll); + void CreateNewTracker(FrameSequenceMetrics::ThreadType thread_type = + FrameSequenceMetrics::ThreadType::kCompositor) { + tracker_ = collection_.StartScrollSequence( + FrameSequenceTrackerType::kTouchScroll, thread_type); } viz::BeginFrameArgs CreateBeginFrameArgs( @@ -100,7 +103,15 @@ class FrameSequenceTrackerTest : public testing::Test { // Check whether a type of tracker exists in |frame_trackers_| or not. bool TrackerExists(FrameSequenceTrackerType type) const { - return collection_.frame_trackers_.contains(type); + auto key = std::make_pair(type, FrameSequenceMetrics::ThreadType::kUnknown); + if (type == FrameSequenceTrackerType::kTouchScroll || + type == FrameSequenceTrackerType::kWheelScroll || + type == FrameSequenceTrackerType::kScrollbarScroll) { + key = std::make_pair(type, FrameSequenceMetrics::ThreadType::kCompositor); + if (!collection_.frame_trackers_.contains(key)) + key = std::make_pair(type, FrameSequenceMetrics::ThreadType::kMain); + } + return collection_.frame_trackers_.contains(key); } bool RemovalTrackerExists(unsigned index, @@ -254,14 +265,29 @@ class FrameSequenceTrackerTest : public testing::Test { return tracker_->ignored_frame_tokens_; } + FrameSequenceMetrics::ThroughputData& ImplThroughput( + FrameSequenceTracker* tracker) const { + return tracker->impl_throughput(); + } + FrameSequenceMetrics::ThroughputData& ImplThroughput() const { return tracker_->impl_throughput(); } + FrameSequenceMetrics::ThroughputData& MainThroughput( + FrameSequenceTracker* tracker) const { + return tracker->main_throughput(); + } + FrameSequenceMetrics::ThroughputData& MainThroughput() const { return tracker_->main_throughput(); } + FrameSequenceMetrics::ThroughputData& AggregatedThroughput( + FrameSequenceTracker* tracker) const { + return tracker->aggregated_throughput(); + } + FrameSequenceMetrics::ThroughputData& AggregatedThroughput() const { return tracker_->aggregated_throughput(); } @@ -464,8 +490,6 @@ TEST_F(FrameSequenceTrackerTest, ReportMetrics) { 1u); histogram_tester.ExpectTotalCount( "Graphics.Smoothness.PercentDroppedFrames.MainThread.TouchScroll", 0u); - histogram_tester.ExpectTotalCount( - "Graphics.Smoothness.PercentDroppedFrames.SlowerThread.TouchScroll", 1u); // Test that both are reported. ImplThroughput().frames_expected = 100u; @@ -478,8 +502,6 @@ TEST_F(FrameSequenceTrackerTest, ReportMetrics) { 2u); histogram_tester.ExpectTotalCount( "Graphics.Smoothness.PercentDroppedFrames.MainThread.TouchScroll", 1u); - histogram_tester.ExpectTotalCount( - "Graphics.Smoothness.PercentDroppedFrames.SlowerThread.TouchScroll", 2u); // Test that none is reported. MainThroughput().frames_expected = 2u; @@ -492,8 +514,6 @@ TEST_F(FrameSequenceTrackerTest, ReportMetrics) { 2u); histogram_tester.ExpectTotalCount( "Graphics.Smoothness.PercentDroppedFrames.MainThread.TouchScroll", 1u); - histogram_tester.ExpectTotalCount( - "Graphics.Smoothness.PercentDroppedFrames.SlowerThread.TouchScroll", 2u); // Test the case where compositor and main thread have the same throughput. ImplThroughput().frames_expected = 120u; @@ -506,8 +526,6 @@ TEST_F(FrameSequenceTrackerTest, ReportMetrics) { 3u); histogram_tester.ExpectTotalCount( "Graphics.Smoothness.PercentDroppedFrames.MainThread.TouchScroll", 2u); - histogram_tester.ExpectTotalCount( - "Graphics.Smoothness.PercentDroppedFrames.SlowerThread.TouchScroll", 3u); } TEST_F(FrameSequenceTrackerTest, ReportMetricsAtFixedInterval) { @@ -638,9 +656,6 @@ TEST_F(FrameSequenceTrackerTest, BeginMainFrameSubmit) { } TEST_F(FrameSequenceTrackerTest, ScrollingThreadMetricCompositorThread) { - tracker_->metrics()->SetScrollingThread( - FrameSequenceMetrics::ThreadType::kCompositor); - // Start with a bunch of frames so that the metric does get reported at the // end of the test. ImplThroughput().frames_expected = 100u; @@ -659,8 +674,7 @@ TEST_F(FrameSequenceTrackerTest, ScrollingThreadMetricCompositorThread) { } TEST_F(FrameSequenceTrackerTest, ScrollingThreadMetricMainThread) { - tracker_->metrics()->SetScrollingThread( - FrameSequenceMetrics::ThreadType::kMain); + CreateNewTracker(FrameSequenceMetrics::ThreadType::kMain); // Start with a bunch of frames so that the metric does get reported at the // end of the test. @@ -1104,8 +1118,8 @@ TEST_F(FrameSequenceTrackerTest, TrackLastImplFrame2) { EXPECT_EQ(NumberOfRemovalTrackers(), 0u); std::string metric = "Graphics.Smoothness.FrameSequenceLength.TouchScroll"; - // Both impl and slower threads reports 101 frames expected. - EXPECT_EQ(histogram_tester.GetBucketCount(metric, 101), 2); + // Impl thread reports 101 frames expected. + EXPECT_EQ(histogram_tester.GetBucketCount(metric, 101), 1); // The main thread reports 0 frames expected. EXPECT_EQ(histogram_tester.GetBucketCount(metric, 0), 1); metric = @@ -1132,8 +1146,8 @@ TEST_F(FrameSequenceTrackerTest, TrackLastImplFrame3) { EXPECT_EQ(NumberOfRemovalTrackers(), 0u); std::string metric = "Graphics.Smoothness.FrameSequenceLength.TouchScroll"; - // Both impl and slower threads reports 101 frames expected. - EXPECT_EQ(histogram_tester.GetBucketCount(metric, 101), 2); + // Impl thread reports 101 frames expected. + EXPECT_EQ(histogram_tester.GetBucketCount(metric, 101), 1); // The main thread reports 0 frames expected. EXPECT_EQ(histogram_tester.GetBucketCount(metric, 0), 1); metric = @@ -1160,8 +1174,8 @@ TEST_F(FrameSequenceTrackerTest, TrackLastImplFrame4) { EXPECT_EQ(NumberOfRemovalTrackers(), 0u); std::string metric = "Graphics.Smoothness.FrameSequenceLength.TouchScroll"; - // Both impl and slower threads reports 101 frames expected. - EXPECT_EQ(histogram_tester.GetBucketCount(metric, 101), 2); + // Impl thread reports 101 frames expected. + EXPECT_EQ(histogram_tester.GetBucketCount(metric, 101), 1); // The main thread reports 0 frames expected. EXPECT_EQ(histogram_tester.GetBucketCount(metric, 0), 1); metric = @@ -1229,8 +1243,8 @@ TEST_F(FrameSequenceTrackerTest, TrackLastImplFrame7) { EXPECT_EQ(NumberOfRemovalTrackers(), 0u); std::string metric = "Graphics.Smoothness.FrameSequenceLength.TouchScroll"; - // Both impl and slower threads reports 101 frames expected. - EXPECT_EQ(histogram_tester.GetBucketCount(metric, 101), 2); + // Impl thread reports 101 frames expected. + EXPECT_EQ(histogram_tester.GetBucketCount(metric, 101), 1); // The main thread reports 0 frames expected. EXPECT_EQ(histogram_tester.GetBucketCount(metric, 0), 1); metric = @@ -1257,8 +1271,8 @@ TEST_F(FrameSequenceTrackerTest, TrackLastImplFrame8) { EXPECT_EQ(NumberOfRemovalTrackers(), 0u); std::string metric = "Graphics.Smoothness.FrameSequenceLength.TouchScroll"; - // Both impl and slower threads reports 101 frames expected. - EXPECT_EQ(histogram_tester.GetBucketCount(metric, 101), 2); + // Impl thread reports 101 frames expected. + EXPECT_EQ(histogram_tester.GetBucketCount(metric, 101), 1); // The main thread reports 0 frames expected. EXPECT_EQ(histogram_tester.GetBucketCount(metric, 0), 1); metric = @@ -1285,8 +1299,8 @@ TEST_F(FrameSequenceTrackerTest, TrackLastImplFrame9) { EXPECT_EQ(NumberOfRemovalTrackers(), 0u); std::string metric = "Graphics.Smoothness.FrameSequenceLength.TouchScroll"; - // Both impl and slower threads reports 101 frames expected. - EXPECT_EQ(histogram_tester.GetBucketCount(metric, 101), 2); + // Impl thread reports 101 frames expected. + EXPECT_EQ(histogram_tester.GetBucketCount(metric, 101), 1); // The main thread reports 0 frames expected. EXPECT_EQ(histogram_tester.GetBucketCount(metric, 0), 1); metric = @@ -1346,8 +1360,8 @@ TEST_F(FrameSequenceTrackerTest, TrackLastImplFrame12) { EXPECT_EQ(NumberOfRemovalTrackers(), 0u); std::string metric = "Graphics.Smoothness.FrameSequenceLength.TouchScroll"; - // Both impl and slower threads reports 101 frames expected. - EXPECT_EQ(histogram_tester.GetBucketCount(metric, 101), 2); + // Impl thread reports 101 frames expected. + EXPECT_EQ(histogram_tester.GetBucketCount(metric, 101), 1); // The main thread reports 0 frames expected. EXPECT_EQ(histogram_tester.GetBucketCount(metric, 0), 1); metric = @@ -1374,8 +1388,8 @@ TEST_F(FrameSequenceTrackerTest, TrackLastImplFrame13) { EXPECT_EQ(NumberOfRemovalTrackers(), 0u); std::string metric = "Graphics.Smoothness.FrameSequenceLength.TouchScroll"; - // Both impl and slower threads reports 101 frames expected. - EXPECT_EQ(histogram_tester.GetBucketCount(metric, 101), 2); + // Impl thread reports 101 frames expected. + EXPECT_EQ(histogram_tester.GetBucketCount(metric, 101), 1); // The main thread reports 0 frames expected. EXPECT_EQ(histogram_tester.GetBucketCount(metric, 0), 1); metric = @@ -1395,8 +1409,8 @@ TEST_F(FrameSequenceTrackerTest, TrackLastImplFrame14) { EXPECT_EQ(NumberOfRemovalTrackers(), 0u); std::string metric = "Graphics.Smoothness.FrameSequenceLength.TouchScroll"; - // Both impl and slower threads reports 101 frames expected. - EXPECT_EQ(histogram_tester.GetBucketCount(metric, 101), 2); + // Impl thread reports 101 frames expected. + EXPECT_EQ(histogram_tester.GetBucketCount(metric, 101), 1); // The main thread reports 0 frames expected. EXPECT_EQ(histogram_tester.GetBucketCount(metric, 0), 1); metric = @@ -1430,8 +1444,8 @@ TEST_F(FrameSequenceTrackerTest, TrackLastImplFrame15) { EXPECT_EQ(NumberOfRemovalTrackers(), 0u); std::string metric = "Graphics.Smoothness.FrameSequenceLength.TouchScroll"; - // Both impl and slower threads reports 101 frames expected. - EXPECT_EQ(histogram_tester.GetBucketCount(metric, 101), 2); + // Impl thread reports 101 frames expected. + EXPECT_EQ(histogram_tester.GetBucketCount(metric, 101), 1); // The main thread reports 0 frames expected. EXPECT_EQ(histogram_tester.GetBucketCount(metric, 0), 1); metric = @@ -1458,8 +1472,8 @@ TEST_F(FrameSequenceTrackerTest, TrackLastImplFrame16) { EXPECT_EQ(NumberOfRemovalTrackers(), 0u); std::string metric = "Graphics.Smoothness.FrameSequenceLength.TouchScroll"; - // Both impl and slower threads reports 101 frames expected. - EXPECT_EQ(histogram_tester.GetBucketCount(metric, 101), 2); + // Impl thread reports 101 frames expected. + EXPECT_EQ(histogram_tester.GetBucketCount(metric, 101), 1); // The main thread reports 0 frames expected. EXPECT_EQ(histogram_tester.GetBucketCount(metric, 0), 1); metric = @@ -1486,8 +1500,8 @@ TEST_F(FrameSequenceTrackerTest, TrackLastImplFrame17) { EXPECT_EQ(NumberOfRemovalTrackers(), 0u); std::string metric = "Graphics.Smoothness.FrameSequenceLength.TouchScroll"; - // Both impl and slower threads reports 101 frames expected. - EXPECT_EQ(histogram_tester.GetBucketCount(metric, 101), 2); + // Impl thread reports 101 frames expected. + EXPECT_EQ(histogram_tester.GetBucketCount(metric, 101), 1); // The main thread reports 0 frames expected. EXPECT_EQ(histogram_tester.GetBucketCount(metric, 0), 1); metric = @@ -1515,8 +1529,8 @@ TEST_F(FrameSequenceTrackerTest, TrackLastImplFrame18) { EXPECT_EQ(NumberOfRemovalTrackers(), 0u); std::string metric = "Graphics.Smoothness.FrameSequenceLength.TouchScroll"; - // Both impl and slower threads reports 101 frames expected. - EXPECT_EQ(histogram_tester.GetBucketCount(metric, 101), 2); + // Impl thread reports 101 frames expected. + EXPECT_EQ(histogram_tester.GetBucketCount(metric, 101), 1); // The main thread reports 0 frames expected. EXPECT_EQ(histogram_tester.GetBucketCount(metric, 0), 1); metric = @@ -1543,8 +1557,8 @@ TEST_F(FrameSequenceTrackerTest, TrackLastImplFrame19) { EXPECT_EQ(NumberOfRemovalTrackers(), 0u); std::string metric = "Graphics.Smoothness.FrameSequenceLength.TouchScroll"; - // Both impl and slower threads reports 101 frames expected. - EXPECT_EQ(histogram_tester.GetBucketCount(metric, 101), 2); + // Impl thread reports 101 frames expected. + EXPECT_EQ(histogram_tester.GetBucketCount(metric, 101), 1); // The main thread reports 0 frames expected. EXPECT_EQ(histogram_tester.GetBucketCount(metric, 0), 1); metric = @@ -1571,8 +1585,8 @@ TEST_F(FrameSequenceTrackerTest, TrackLastImplFrame20) { EXPECT_EQ(NumberOfRemovalTrackers(), 0u); std::string metric = "Graphics.Smoothness.FrameSequenceLength.TouchScroll"; - // Both impl and slower threads reports 101 frames expected. - EXPECT_EQ(histogram_tester.GetBucketCount(metric, 101), 2); + // Impl thread reports 101 frames expected. + EXPECT_EQ(histogram_tester.GetBucketCount(metric, 101), 1); // The main thread reports 0 frames expected. EXPECT_EQ(histogram_tester.GetBucketCount(metric, 0), 1); metric = @@ -1851,7 +1865,8 @@ TEST_F(FrameSequenceTrackerTest, MainThreadPresentWithNullTimeStamp) { const char sequence[] = "b(1)B(0,1)E(1)s(1)S(1)e(1,1)"; GenerateSequence(sequence); collection_.NotifyFramePresented( - 1, {base::TimeTicks(), viz::BeginFrameArgs::DefaultInterval(), 0}); + 1, {base::TimeTicks(), viz::BeginFrameArgs::DefaultInterval(), + gfx::PresentationFeedback::kFailure}); EXPECT_EQ(MainThroughput().frames_expected, 1u); // No presentation, no main frame produced. EXPECT_EQ(MainThroughput().frames_produced, 0u); @@ -1869,6 +1884,29 @@ TEST_F(FrameSequenceTrackerTest, TrackerTypeEncoding) { EXPECT_EQ(active_encoded, 16); // 1 << 4 } +TEST_F(FrameSequenceTrackerTest, UniversalTrackerSubmitThroughput) { + auto recorder = std::make_unique<ukm::TestUkmRecorder>(); + auto ukm_manager = std::make_unique<UkmManager>(std::move(recorder)); + + collection_.ClearAll(); + collection_.SetUkmManager(ukm_manager.get()); + auto* tracker = + collection_.StartSequence(FrameSequenceTrackerType::kUniversal); + ImplThroughput(tracker).frames_expected = 200u; + ImplThroughput(tracker).frames_produced = 190u; + MainThroughput(tracker).frames_expected = 100u; + MainThroughput(tracker).frames_produced = 50u; + AggregatedThroughput(tracker).frames_expected = 200u; + AggregatedThroughput(tracker).frames_produced = 150u; + + collection_.ComputeUniversalThroughputForTesting(); + DCHECK(collection_.HasThroughputData()); + EXPECT_EQ(collection_.TakeLastAggregatedPercent(), 25); + EXPECT_EQ(collection_.TakeLastImplPercent(), 5); + EXPECT_EQ(collection_.TakeLastMainPercent().value(), 50); + EXPECT_FALSE(collection_.HasThroughputData()); +} + TEST_F(FrameSequenceTrackerTest, CustomTrackers) { // Start custom tracker 1. collection_.StartCustomSequence(1); @@ -1918,4 +1956,104 @@ TEST_F(FrameSequenceTrackerTest, CustomTrackers) { EXPECT_EQ(1u, results[3].frames_expected); } +TEST_F(FrameSequenceTrackerTest, MergeTrackers) { + // Generate two sequences of scrolls: first with only 1 frame, and then with + // 99 frames. Verify that the two scrolls are merged to report a single + // metric. + base::HistogramTester histogram_tester; + const char first_sequence[] = "b(1)s(1)e(1,0)P(1)"; + GenerateSequence(first_sequence); + EXPECT_EQ(ImplThroughput().frames_expected, 1u); + EXPECT_EQ(ImplThroughput().frames_produced, 1u); + collection_.StopSequence(FrameSequenceTrackerType::kTouchScroll); + + const char metric[] = + "Graphics.Smoothness.PercentDroppedFrames.CompositorThread.TouchScroll"; + histogram_tester.ExpectTotalCount(metric, 0u); + EXPECT_FALSE(TrackerExists(FrameSequenceTrackerType::kTouchScroll)); + + CreateNewTracker(); + const char second_sequence[] = "b(2)s(2)e(2,0)P(2)b(100)s(3)e(100,0)P(3)"; + GenerateSequence(second_sequence); + EXPECT_EQ(ImplThroughput().frames_expected, 99u); + EXPECT_EQ(ImplThroughput().frames_produced, 2u); + collection_.StopSequence(FrameSequenceTrackerType::kTouchScroll); + EXPECT_FALSE(TrackerExists(FrameSequenceTrackerType::kTouchScroll)); + histogram_tester.ExpectTotalCount(metric, 1u); + EXPECT_THAT(histogram_tester.GetAllSamples(metric), + testing::ElementsAre(base::Bucket(97, 1))); +} + +TEST_F(FrameSequenceTrackerTest, MergeTrackersPresentAfterStopSequence) { + // Generate two sequences of scrolls: first with only 1 frame, and then with + // 99 frames. Verify that the two scrolls are merged to report a single + // metric. For the second sequence, the last frame is presented after the + // sequence ends. + base::HistogramTester histogram_tester; + const char first_sequence[] = "b(1)s(1)e(1,0)P(1)"; + GenerateSequence(first_sequence); + EXPECT_EQ(ImplThroughput().frames_expected, 1u); + EXPECT_EQ(ImplThroughput().frames_produced, 1u); + collection_.StopSequence(FrameSequenceTrackerType::kTouchScroll); + + const char metric[] = + "Graphics.Smoothness.PercentDroppedFrames.CompositorThread.TouchScroll"; + histogram_tester.ExpectTotalCount(metric, 0u); + EXPECT_FALSE(TrackerExists(FrameSequenceTrackerType::kTouchScroll)); + + CreateNewTracker(); + const char second_sequence[] = "b(2)s(2)e(2,0)P(2)b(100)s(3)e(100,0)"; + GenerateSequence(second_sequence); + EXPECT_EQ(ImplThroughput().frames_expected, 99u); + EXPECT_EQ(ImplThroughput().frames_produced, 1u); + collection_.StopSequence(FrameSequenceTrackerType::kTouchScroll); + GenerateSequence("P(3)"); + histogram_tester.ExpectTotalCount(metric, 1u); + EXPECT_THAT(histogram_tester.GetAllSamples(metric), + testing::ElementsAre(base::Bucket(97, 1))); +} + +TEST_F(FrameSequenceTrackerTest, MergeTrackersScrollOnSameThread) { + // Do a short scroll on the compositor thread, then do another short scroll on + // the compositor thread. Make sure these are merged. + base::HistogramTester histogram_tester; + const char first_sequence[] = "b(1)s(1)e(1,0)P(1)b(80)s(2)e(80,0)P(2)"; + GenerateSequence(first_sequence); + collection_.StopSequence(FrameSequenceTrackerType::kTouchScroll); + + CreateNewTracker(FrameSequenceMetrics::ThreadType::kCompositor); + const char second_sequence[] = "b(81)s(3)e(81,0)P(3)b(101)s(4)e(101,0)P(4)"; + GenerateSequence(second_sequence); + collection_.StopSequence(FrameSequenceTrackerType::kTouchScroll); + + const char comp_metric[] = + "Graphics.Smoothness.PercentDroppedFrames.CompositorThread.TouchScroll"; + const char main_metric[] = + "Graphics.Smoothness.PercentDroppedFrames.MainThread.TouchScroll"; + histogram_tester.ExpectTotalCount(comp_metric, 1u); + histogram_tester.ExpectTotalCount(main_metric, 0u); +} + +TEST_F(FrameSequenceTrackerTest, MergeTrackersScrollOnDifferentThreads) { + // Do a short scroll on the compositor thread, then do another short scroll on + // the main-thread. Make sure these are not merged. + base::HistogramTester histogram_tester; + const char compscroll_sequence[] = "b(1)s(1)e(1,0)P(1)b(80)s(2)e(80,0)P(2)"; + GenerateSequence(compscroll_sequence); + collection_.StopSequence(FrameSequenceTrackerType::kTouchScroll); + + CreateNewTracker(FrameSequenceMetrics::ThreadType::kMain); + const char mainscroll_sequence[] = + "b(81)s(3)e(81,0)P(3)b(101)s(4)e(101,0)P(4)"; + GenerateSequence(mainscroll_sequence); + collection_.StopSequence(FrameSequenceTrackerType::kTouchScroll); + + const char comp_metric[] = + "Graphics.Smoothness.PercentDroppedFrames.CompositorThread.TouchScroll"; + const char main_metric[] = + "Graphics.Smoothness.PercentDroppedFrames.MainThread.TouchScroll"; + histogram_tester.ExpectTotalCount(comp_metric, 0u); + histogram_tester.ExpectTotalCount(main_metric, 0u); +} + } // namespace cc diff --git a/chromium/cc/metrics/latency_ukm_reporter.cc b/chromium/cc/metrics/latency_ukm_reporter.cc index 072ccb31daf..5155ee6513c 100644 --- a/chromium/cc/metrics/latency_ukm_reporter.cc +++ b/chromium/cc/metrics/latency_ukm_reporter.cc @@ -4,57 +4,108 @@ #include "cc/metrics/latency_ukm_reporter.h" +#include <climits> +#include <memory> + #include "base/rand_util.h" #include "cc/trees/ukm_manager.h" namespace cc { -void LatencyUkmReporter::ReportLatencyUkm( +// We use a Poisson process with an exponential decay multiplier. The goal is to +// get many randomly distributed samples early during page load and initial +// interaction, then samples at an exponentially decreasing rate to effectively +// cap the number of samples. The particular parameters chosen here give roughly +// 5-10 samples in the first 100 frames, decaying to several hours between +// samples by the 40th sample. The multiplier value should be tuned to achieve a +// total sample count that avoids throttling by the UKM system. +class LatencyUkmReporter::SamplingController { + public: + SamplingController() = default; + ~SamplingController() = default; + + // When a new UKM event is issued, this function should be called (once and + // only once) by the client to determine whether that event should be recorded + // or ignored, according to the sampling parameters. The sampling state will + // be updated to be ready for the next UKM event. + bool ShouldRecordNextEvent() { + bool should_record = false; + if (!frames_to_next_event_) { + should_record = true; + frames_to_next_event_ = SampleFramesToNextEvent(); + } + DCHECK_GT(frames_to_next_event_, 0); + --frames_to_next_event_; + return should_record; + } + + private: + // The |kSampleRateMultiplier| and |kSampleDecayRate| have been set to meet + // UKM goals for data volume. + const double kSampleDecayRate = 1.0; + const double kSampleRateMultiplier = 2.0; + + int SampleFramesToNextEvent() { + // Sample from an exponential distribution to give a Poisson distribution + // of samples per time unit, then weigh it with an exponential multiplier + // to give a few samples in rapid succession (for frames early in the + // page's life) then exponentially fewer as the page lives longer. + // RandDouble() returns [0,1), but we need (0,1]. If RandDouble() is + // uniformly random, so is 1-RandDouble(), so use it to adjust the range. + // When RandDouble() returns 0.0, as it could, we will get a float_sample + // of 0, causing underflow. So rejection sample until we get a positive + // count. + double float_sample = 0.0; + do { + float_sample = -(kSampleRateMultiplier * + std::exp(samples_so_far_ * kSampleDecayRate) * + std::log(1.0 - base::RandDouble())); + } while (float_sample == 0.0); + // float_sample is positive, so we don't need to worry about underflow. + // After around 30 samples we will end up with a super high sample. That's + // OK because it just means we'll stop reporting metrics for that session, + // but we do need to be careful about overflow and NaN. + samples_so_far_++; + int integer_sample = + std::isnan(float_sample) + ? INT_MAX + : base::saturated_cast<int>(std::ceil(float_sample)); + return integer_sample; + } + + int samples_so_far_ = 0; + int frames_to_next_event_ = 0; +}; + +LatencyUkmReporter::LatencyUkmReporter() + : compositor_latency_sampling_controller_( + std::make_unique<SamplingController>()), + event_latency_sampling_controller_( + std::make_unique<SamplingController>()) {} + +LatencyUkmReporter::~LatencyUkmReporter() = default; + +void LatencyUkmReporter::ReportCompositorLatencyUkm( CompositorFrameReporter::FrameReportType report_type, const std::vector<CompositorFrameReporter::StageData>& stage_history, const CompositorFrameReporter::ActiveTrackers& active_trackers, const viz::FrameTimingDetails& viz_breakdown) { - if (!ukm_manager_) - return; - if (!frames_to_next_event_) { - ukm_manager_->RecordLatencyUKM(report_type, stage_history, active_trackers, - viz_breakdown); - frames_to_next_event_ += SampleFramesToNextEvent(); + if (ukm_manager_ && + compositor_latency_sampling_controller_->ShouldRecordNextEvent()) { + ukm_manager_->RecordCompositorLatencyUKM(report_type, stage_history, + active_trackers, viz_breakdown); } - DCHECK_GT(frames_to_next_event_, 0u); - --frames_to_next_event_; } -unsigned LatencyUkmReporter::SampleFramesToNextEvent() { - // Return the test interval if set - if (frames_to_next_event_for_test_) - return frames_to_next_event_for_test_; - - // Sample from an exponential distribution to give a poisson distribution - // of samples per time unit, then weigh it with an exponential multiplier to - // give a few samples in rapid succession (for frames early in the page's - // life) then exponentially fewer as the page lives longer. - // RandDouble() returns [0,1), but we need (0,1]. If RandDouble() is - // uniformly random, so is 1-RandDouble(), so use it to adjust the range. - // When RandDouble returns 0.0, as it could, we will get a float_sample of - // 0, causing underflow in UpdateEventTimeAndRecordIfNeeded. So rejection - // sample until we get a positive count. - double float_sample = 0; - do { - float_sample = -(sample_rate_multiplier_ * - std::exp(samples_so_far_ * sample_decay_rate_) * - std::log(1.0 - base::RandDouble())); - } while (float_sample == 0); - // float_sample is positive, so we don't need to worry about underflow. - // After around 30 samples we will end up with a super high - // sample. That's OK because it just means we'll stop reporting metrics - // for that session, but we do need to be careful about overflow and NaN. - samples_so_far_++; - unsigned unsigned_sample = - std::isnan(float_sample) - ? UINT_MAX - : base::saturated_cast<unsigned>(std::ceil(float_sample)); - return unsigned_sample; +void LatencyUkmReporter::ReportEventLatencyUkm( + const std::vector<EventMetrics>& events_metrics, + const std::vector<CompositorFrameReporter::StageData>& stage_history, + const viz::FrameTimingDetails& viz_breakdown) { + if (ukm_manager_ && + event_latency_sampling_controller_->ShouldRecordNextEvent()) { + ukm_manager_->RecordEventLatencyUKM(events_metrics, stage_history, + viz_breakdown); + } } } // namespace cc diff --git a/chromium/cc/metrics/latency_ukm_reporter.h b/chromium/cc/metrics/latency_ukm_reporter.h index 3ee27823a4b..e5b0a8ac273 100644 --- a/chromium/cc/metrics/latency_ukm_reporter.h +++ b/chromium/cc/metrics/latency_ukm_reporter.h @@ -5,9 +5,12 @@ #ifndef CC_METRICS_LATENCY_UKM_REPORTER_H_ #define CC_METRICS_LATENCY_UKM_REPORTER_H_ -#include "base/optional.h" +#include <memory> +#include <vector> + #include "cc/cc_export.h" #include "cc/metrics/compositor_frame_reporter.h" +#include "cc/metrics/event_metrics.h" #include "components/viz/common/frame_timing_details.h" namespace cc { @@ -17,25 +20,27 @@ class UkmManager; // talks to UkmManager to report it. class CC_EXPORT LatencyUkmReporter { public: - LatencyUkmReporter() = default; - ~LatencyUkmReporter() = default; + LatencyUkmReporter(); + ~LatencyUkmReporter(); + + LatencyUkmReporter(const LatencyUkmReporter&) = delete; + LatencyUkmReporter& operator=(const LatencyUkmReporter&) = delete; - void ReportLatencyUkm( + void ReportCompositorLatencyUkm( CompositorFrameReporter::FrameReportType report_type, const std::vector<CompositorFrameReporter::StageData>& stage_history, const CompositorFrameReporter::ActiveTrackers& active_trackers, const viz::FrameTimingDetails& viz_breakdown); - void SetUkmManager(UkmManager* manager) { ukm_manager_ = manager; } + void ReportEventLatencyUkm( + const std::vector<EventMetrics>& events_metrics, + const std::vector<CompositorFrameReporter::StageData>& stage_history, + const viz::FrameTimingDetails& viz_breakdown); - private: - unsigned SampleFramesToNextEvent(); + void set_ukm_manager(UkmManager* manager) { ukm_manager_ = manager; } - // To test event sampling. This and all future intervals will be the given - // frame count, until this is called again. - void FramesToNextEventForTest(unsigned num_frames) { - frames_to_next_event_for_test_ = num_frames; - } + private: + class SamplingController; // This is pointing to the LayerTreeHostImpl::ukm_manager_, which is // initialized right after the LayerTreeHostImpl is created. So when this @@ -44,23 +49,8 @@ class CC_EXPORT LatencyUkmReporter { // this pointer should never be null as long as LayerTreeHostImpl is alive. UkmManager* ukm_manager_ = nullptr; - // Sampling control. We use a Poisson process with an exponential decay - // multiplier. The goal is to get many randomly distributed samples early - // during page load and initial interaction, then samples at an exponentially - // decreasing rate to effectively cap the number of samples. The particular - // parameters chosen here give roughly 5-10 samples in the first 100 frames, - // decaying to several hours between samples by the 40th sample. The - // multiplier value should be tuned to achieve a total sample count that - // avoids throttling by the UKM system. - // The sample_rate_multiplier_ and sample_decay_rate_ have been set to meet - // UKM goals for data volume. - double sample_decay_rate_ = 1; - double sample_rate_multiplier_ = 2; - unsigned samples_so_far_ = 0; - unsigned frames_to_next_event_ = 0; - - // Test data, used for SampleFramesToNextEvent if present - unsigned frames_to_next_event_for_test_ = 0; + std::unique_ptr<SamplingController> compositor_latency_sampling_controller_; + std::unique_ptr<SamplingController> event_latency_sampling_controller_; }; } // namespace cc diff --git a/chromium/cc/metrics/throughput_ukm_reporter.cc b/chromium/cc/metrics/throughput_ukm_reporter.cc index 08369a64156..39f1a33f7ce 100644 --- a/chromium/cc/metrics/throughput_ukm_reporter.cc +++ b/chromium/cc/metrics/throughput_ukm_reporter.cc @@ -30,6 +30,11 @@ void ThroughputUkmReporter::ReportThroughputUkm( const base::Optional<int>& impl_throughput_percent, const base::Optional<int>& main_throughput_percent, FrameSequenceTrackerType type) { + // It is possible that when a tab shuts down, the ukm_manager_ owned by the + // LayerTreeHostImpl is cleared, and yet we try to report to UKM here. In this + // case, the |ukm_manager_| here is null. + if (!ukm_manager_) + return; if (samples_to_next_event_[static_cast<int>(type)] == 0) { // Sample every 100 events. Using the Universal tracker as an example // which reports UMA every 5s, then the system collects UKM once per @@ -65,4 +70,42 @@ void ThroughputUkmReporter::ReportAggregateThroughput( --samples_for_aggregated_report_; } +void ThroughputUkmReporter::ComputeUniversalThroughput( + FrameSequenceMetrics* metrics) { + last_impl_percent_ = metrics->impl_throughput().DroppedFramePercent(); + last_main_percent_ = metrics->main_throughput().DroppedFramePercent(); + last_aggregated_percent_ = + metrics->aggregated_throughput().DroppedFramePercent(); +} + +bool ThroughputUkmReporter::HasThroughputData() const { + return last_aggregated_percent_.has_value(); +} + +int ThroughputUkmReporter::TakeLastAggregatedPercent() { + int ret_value = last_aggregated_percent_.value(); + DCHECK(ret_value >= 0 && ret_value <= 100); + last_aggregated_percent_ = base::nullopt; + return ret_value; +} + +int ThroughputUkmReporter::TakeLastImplPercent() { + int ret_value = last_impl_percent_.value(); + DCHECK(ret_value >= 0 && ret_value <= 100); + last_impl_percent_ = base::nullopt; + return ret_value; +} + +base::Optional<int> ThroughputUkmReporter::TakeLastMainPercent() { + base::Optional<int> ret_value = last_main_percent_; + DCHECK(!ret_value || (ret_value.value() >= 0 && ret_value.value() <= 100)); + last_main_percent_ = base::nullopt; + return ret_value; +} + +uint32_t ThroughputUkmReporter::GetSamplesToNextEventForTesting(int index) { + DCHECK_LT(index, static_cast<int>(FrameSequenceTrackerType::kMaxType)); + return samples_to_next_event_[index]; +} + } // namespace cc diff --git a/chromium/cc/metrics/throughput_ukm_reporter.h b/chromium/cc/metrics/throughput_ukm_reporter.h index 441be89f88a..c92fdfe33b4 100644 --- a/chromium/cc/metrics/throughput_ukm_reporter.h +++ b/chromium/cc/metrics/throughput_ukm_reporter.h @@ -36,6 +36,23 @@ class CC_EXPORT ThroughputUkmReporter { void ReportAggregateThroughput(AggregationType aggregation_type, int throughput); + void ComputeUniversalThroughput(FrameSequenceMetrics* metrics); + + // Once the kUniversal tracker reported its throughput to UMA, this returns + // true. In this case, the |last_aggregated_percent_| and |last_impl_percent_| + // must have value. + bool HasThroughputData() const; + // These functions are called only when HasThroughputData is true. They return + // the throughput value of the corresponding thread, and reset them to nullopt + // to indicate the value has been reported. + int TakeLastAggregatedPercent(); + int TakeLastImplPercent(); + // This could be nullopt even if the HasThroughputData() is true, because it + // could happen that all the frames are generated from the compositor thread. + base::Optional<int> TakeLastMainPercent(); + + uint32_t GetSamplesToNextEventForTesting(int index); + private: // Sampling control. We sample the event here to not throttle the UKM system. // Currently, the same sampling rate is applied to all existing trackers. We @@ -46,10 +63,20 @@ class CC_EXPORT ThroughputUkmReporter { // This is pointing to the LayerTreeHostImpl::ukm_manager_, which is // initialized right after the LayerTreeHostImpl is created. So when this - // pointer is initialized, there should be no trackers yet. Moreover, the - // LayerTreeHostImpl::ukm_manager_ lives as long as the LayerTreeHostImpl, so - // this pointer should never be null as long as LayerTreeHostImpl is alive. + // pointer is initialized, there should be no trackers yet. UkmManager* const ukm_manager_; + + // The last "PercentDroppedFrames" reported to UMA. LayerTreeHostImpl will + // read this number and send to the GPU process. When this page is done, we + // will report to UKM using these numbers. Currently only meaningful to the + // kUniversal tracker. + // Possible values: + // 1. A non-negative int value which is the percent of frames dropped. + // 2. base::nullopt: when they are fetched by LayerTreeHostImpl, so that it + // knows that the last value has been reported. + base::Optional<int> last_aggregated_percent_; + base::Optional<int> last_main_percent_; + base::Optional<int> last_impl_percent_; }; } // namespace cc diff --git a/chromium/cc/metrics/video_playback_roughness_reporter.cc b/chromium/cc/metrics/video_playback_roughness_reporter.cc index 8e76345e537..a7b77e99a65 100644 --- a/chromium/cc/metrics/video_playback_roughness_reporter.cc +++ b/chromium/cc/metrics/video_playback_roughness_reporter.cc @@ -4,6 +4,8 @@ #include "cc/metrics/video_playback_roughness_reporter.h" +#include <algorithm> + #include "base/bind_helpers.h" #include "base/metrics/histogram_macros.h" #include "components/viz/common/quads/compositor_frame_metadata.h" @@ -28,6 +30,7 @@ constexpr int VideoPlaybackRoughnessReporter::kMaxWindowSize; constexpr int VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit; constexpr int VideoPlaybackRoughnessReporter::kMinWindowsBeforeSubmit; constexpr int VideoPlaybackRoughnessReporter::kPercentileToSubmit; +constexpr double VideoPlaybackRoughnessReporter::kDesiredWindowDuration; VideoPlaybackRoughnessReporter::VideoPlaybackRoughnessReporter( PlaybackRoughnessReportingCallback reporting_cb) @@ -37,8 +40,7 @@ VideoPlaybackRoughnessReporter::~VideoPlaybackRoughnessReporter() = default; double VideoPlaybackRoughnessReporter::ConsecutiveFramesWindow::roughness() const { - return root_mean_square_error.InMicrosecondsF() / - intended_duration.InMicrosecondsF(); + return root_mean_square_error.InMillisecondsF(); } VideoPlaybackRoughnessReporter::FrameInfo::FrameInfo() = default; @@ -56,17 +58,10 @@ void VideoPlaybackRoughnessReporter::FrameSubmitted( FrameInfo info; info.token = token; - info.decode_time.emplace(); - if (!frame.metadata()->GetTimeTicks( - media::VideoFrameMetadata::DECODE_END_TIME, - &info.decode_time.value())) { - info.decode_time.reset(); - } + info.decode_time = frame.metadata()->decode_end_time; - info.intended_duration.emplace(); - if (frame.metadata()->GetTimeDelta( - media::VideoFrameMetadata::WALLCLOCK_FRAME_DURATION, - &info.intended_duration.value())) { + info.intended_duration = frame.metadata()->wallclock_frame_duration; + if (info.intended_duration) { if (render_interval > info.intended_duration.value()) { // In videos with FPS higher than display refresh rate we acknowledge // the fact that some frames will be dropped upstream and frame's intended @@ -74,27 +69,28 @@ void VideoPlaybackRoughnessReporter::FrameSubmitted( info.intended_duration = render_interval; } - // Adjust frame window size to fit about 0.5 seconds of playback - double win_size = - std::round(0.5 / info.intended_duration.value().InSecondsF()); + // Adjust frame window size to fit about 1 second of playback + double win_size = std::round(kDesiredWindowDuration / + info.intended_duration.value().InSecondsF()); frames_window_size_ = std::max(kMinWindowSize, static_cast<int>(win_size)); frames_window_size_ = std::min(frames_window_size_, kMaxWindowSize); - } else { - info.intended_duration.reset(); } + frames_.push_back(info); } void VideoPlaybackRoughnessReporter::FramePresented(TokenType token, - base::TimeTicks timestamp) { + base::TimeTicks timestamp, + bool reliable_timestamp) { for (auto it = frames_.rbegin(); it != frames_.rend(); it++) { FrameInfo& info = *it; if (token == it->token) { - info.presentation_time = timestamp; if (info.decode_time.has_value()) { auto time_since_decode = timestamp - info.decode_time.value(); UMA_HISTOGRAM_TIMES("Media.VideoFrameSubmitter", time_since_decode); } + if (reliable_timestamp) + info.presentation_time = timestamp; break; } if (viz::FrameTokenGT(token, it->token)) diff --git a/chromium/cc/metrics/video_playback_roughness_reporter.h b/chromium/cc/metrics/video_playback_roughness_reporter.h index caddf6a3922..d001df91d7c 100644 --- a/chromium/cc/metrics/video_playback_roughness_reporter.h +++ b/chromium/cc/metrics/video_playback_roughness_reporter.h @@ -25,7 +25,7 @@ using PlaybackRoughnessReportingCallback = base::RepeatingCallback< // This class tracks moments when each frame was submitted // and when it was displayed. Then series of frames split into groups -// of consecutive frames, where each group takes about 1/2 second of playback. +// of consecutive frames, where each group takes about one second of playback. // Such groups also called 'frame windows'. Each windows is assigned a roughness // score that measures how far playback smoothness was from the ideal playback. // Information about several windows and their roughness score is aggregated @@ -46,7 +46,9 @@ class CC_EXPORT VideoPlaybackRoughnessReporter { void FrameSubmitted(TokenType token, const media::VideoFrame& frame, base::TimeDelta render_interval); - void FramePresented(TokenType token, base::TimeTicks timestamp); + void FramePresented(TokenType token, + base::TimeTicks timestamp, + bool reliable_timestamp); void ProcessFrameWindow(); void Reset(); @@ -54,13 +56,13 @@ class CC_EXPORT VideoPlaybackRoughnessReporter { static constexpr int kMinWindowSize = 6; // An upper bund on how many frames can be in ConsecutiveFramesWindow - static constexpr int kMaxWindowSize = 40; + static constexpr int kMaxWindowSize = 60; // How many frame windows should be observed before reporting smoothness // due to playback time. - // 1/2 a second per window, 200 windows. It means smoothness will be reported + // 1 second per window, 100 windows. It means smoothness will be reported // for every 100 seconds of playback. - static constexpr int kMaxWindowsBeforeSubmit = 200; + static constexpr int kMaxWindowsBeforeSubmit = 100; // How many frame windows should be observed to report soothness on last // time before the destruction of the reporter. @@ -72,6 +74,13 @@ class CC_EXPORT VideoPlaybackRoughnessReporter { static_assert(kPercentileToSubmit > 0 && kPercentileToSubmit < 100, "invalid percentile value"); + // Desired duration of ConsecutiveFramesWindow in seconds. + // This value and the video FPS are being used when calculating actual + // number of frames in the ConsecutiveFramesWindow. + // kMinWindowSize and kMaxWindowSize put bounds to the window length + // and superseed this value. + static constexpr double kDesiredWindowDuration = 1.0; + private: friend class VideoPlaybackRoughnessReporterTest; struct FrameInfo { diff --git a/chromium/cc/metrics/video_playback_roughness_reporter_unittest.cc b/chromium/cc/metrics/video_playback_roughness_reporter_unittest.cc index e93cbfc3d61..053c1e77d35 100644 --- a/chromium/cc/metrics/video_playback_roughness_reporter_unittest.cc +++ b/chromium/cc/metrics/video_playback_roughness_reporter_unittest.cc @@ -5,6 +5,7 @@ #include "cc/metrics/video_playback_roughness_reporter.h" #include <algorithm> +#include <memory> #include <random> #include <vector> @@ -36,16 +37,21 @@ class VideoPlaybackRoughnessReporterTest : public ::testing::Test { scoped_refptr<VideoFrame> MakeFrame(base::TimeDelta duration) { scoped_refptr<VideoFrame> result = media::VideoFrame::CreateColorFrame( gfx::Size(4, 4), 0x80, 0x80, 0x80, base::TimeDelta()); - result->metadata()->SetTimeDelta( - VideoFrameMetadata::WALLCLOCK_FRAME_DURATION, duration); + result->metadata()->wallclock_frame_duration = duration; return result; } ::testing::AssertionResult CheckSizes() { - if (reporter()->frames_.size() > 80u) - return ::testing::AssertionFailure() - << "frames " << reporter()->frames_.size(); - if (reporter()->worst_windows_.size() > 20u) + size_t max_frames = + 2 * size_t{VideoPlaybackRoughnessReporter::kMaxWindowSize}; + if (reporter()->frames_.size() > max_frames) + return ::testing::AssertionFailure(); + + constexpr int max_worst_windows_size = + 1 + VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit * + (100 - VideoPlaybackRoughnessReporter::kPercentileToSubmit) / + 100; + if (reporter()->worst_windows_.size() > max_worst_windows_size) return ::testing::AssertionFailure() << "windows " << reporter()->worst_windows_.size(); return ::testing::AssertionSuccess(); @@ -60,7 +66,7 @@ class VideoPlaybackRoughnessReporterTest : public ::testing::Test { base::TimeDelta duration = vsync * frame_cadence; auto frame = MakeFrame(ideal_duration); reporter()->FrameSubmitted(idx, *frame, vsync); - reporter()->FramePresented(idx, time); + reporter()->FramePresented(idx, time, true); reporter()->ProcessFrameWindow(); time += duration; } @@ -83,7 +89,7 @@ class VideoPlaybackRoughnessReporterTest : public ::testing::Test { int presented_idx = idx - i; int frame_cadence = cadence[presented_idx % cadence.size()]; base::TimeDelta duration = vsync * frame_cadence; - reporter()->FramePresented(presented_idx, time); + reporter()->FramePresented(presented_idx, time, true); time += duration; } } @@ -98,13 +104,13 @@ TEST_F(VideoPlaybackRoughnessReporterTest, BestCase24fps) { int fps = 24; SetReportingCallabck( [&](int size, base::TimeDelta duration, double roughness) { - ASSERT_EQ(size, fps / 2); - ASSERT_NEAR(duration.InMillisecondsF(), 500.0, 1.0); - ASSERT_NEAR(roughness, 0.0118, 0.0001); + ASSERT_EQ(size, fps); + ASSERT_NEAR(duration.InMillisecondsF(), 1000.0, 1.0); + ASSERT_NEAR(roughness, 5.9, 0.1); call_count++; }); int frames_to_run = - VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit * fps / 2 + 10; + VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit * fps + 10; NormalRun(fps, 60, {2, 3}, frames_to_run); EXPECT_EQ(call_count, 1); } @@ -114,29 +120,90 @@ TEST_F(VideoPlaybackRoughnessReporterTest, BestCase24fpsOn120Hz) { int fps = 24; SetReportingCallabck( [&](int size, base::TimeDelta duration, double roughness) { - ASSERT_EQ(size, fps / 2); - ASSERT_NEAR(duration.InMillisecondsF(), 500.0, 1.0); - ASSERT_NEAR(roughness, 0.0, 0.0001); + ASSERT_EQ(size, fps); + ASSERT_NEAR(duration.InMillisecondsF(), 1000.0, 1.0); + ASSERT_NEAR(roughness, 0.0, 0.1); call_count++; }); int frames_to_run = - VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit * fps / 2 + 10; + VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit * fps + 10; NormalRun(fps, 120, {5}, frames_to_run); EXPECT_EQ(call_count, 1); } +TEST_F(VideoPlaybackRoughnessReporterTest, BestCase30fps) { + int call_count = 0; + int fps = 30; + SetReportingCallabck( + [&](int size, base::TimeDelta duration, double roughness) { + ASSERT_EQ(size, fps); + ASSERT_NEAR(duration.InMillisecondsF(), 1000.0, 1.0); + ASSERT_NEAR(roughness, 0.0, 0.1); + call_count++; + }); + int frames_to_run = + VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit * fps + 1; + NormalRun(fps, 60, {2}, frames_to_run); + EXPECT_EQ(call_count, 1); +} + +// This cadence pattern was used in the small user study and was found +// to be perceived by participants as not as good as ideal 30fps playback but +// better than the pattern from UserStudyBad. +// The main characteristic of this test is that cadence breaks by having a frame +// shown only once, but the very next frame is being shown 3 times thus +// fixing the synchronization. +TEST_F(VideoPlaybackRoughnessReporterTest, UserStudyOkay) { + int call_count = 0; + int fps = 30; + SetReportingCallabck( + [&](int size, base::TimeDelta duration, double roughness) { + ASSERT_EQ(size, fps); + ASSERT_NEAR(duration.InMillisecondsF(), 1000.0, 1.0); + ASSERT_NEAR(roughness, 4.3, 0.1); + call_count++; + }); + int frames_to_run = + VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit * fps + 1; + NormalRun(fps, 60, {2, 2, 2, 2, 2, 2, 1, 3, 2, 2, 2, 2, 2, 2, 2}, + frames_to_run); + EXPECT_EQ(call_count, 1); +} + +// This cadence pattern was used in the small user study and was found +// to be perceived as worst of all options in the study. +// The main characteristic of this test is that cadence breaks by having a frame +// shown only once, and it takes 2 more frames for a frame that is shown 3 times +// thus fixing the synchronization. +TEST_F(VideoPlaybackRoughnessReporterTest, UserStudyBad) { + int call_count = 0; + int fps = 30; + SetReportingCallabck( + [&](int size, base::TimeDelta duration, double roughness) { + ASSERT_EQ(size, fps); + ASSERT_NEAR(duration.InMillisecondsF(), 1000.0, 1.0); + ASSERT_NEAR(roughness, 7.46, 0.1); + call_count++; + }); + int frames_to_run = + VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit * fps + 1; + NormalRun(fps, 60, {2, 2, 2, 2, 2, 1, 2, 2, 3, 2, 2, 2, 2, 2, 2}, + frames_to_run); + EXPECT_EQ(call_count, 1); +} + TEST_F(VideoPlaybackRoughnessReporterTest, Glitchy24fps) { int call_count = 0; int fps = 24; SetReportingCallabck( [&](int size, base::TimeDelta duration, double roughness) { - ASSERT_EQ(size, fps / 2); - ASSERT_NEAR(duration.InMillisecondsF(), 500.0, 1.0); - ASSERT_NEAR(roughness, 0.0264, 0.0001); + ASSERT_EQ(size, fps); + ASSERT_NEAR(duration.InMillisecondsF(), 1000.0, 1.0); + ASSERT_NEAR(roughness, 14.8, 0.1); call_count++; }); int frames_to_run = - VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit * fps / 2 + 1; + VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit * fps + 1; NormalRun(fps, 60, {2, 3, 1, 3, 2, 4, 2, 3, 2, 3, 3, 3}, frames_to_run); EXPECT_EQ(call_count, 1); } @@ -146,13 +213,13 @@ TEST_F(VideoPlaybackRoughnessReporterTest, BestCase60fps) { int fps = 60; SetReportingCallabck( [&](int size, base::TimeDelta duration, double roughness) { - ASSERT_EQ(size, fps / 2); - ASSERT_NEAR(duration.InMillisecondsF(), 500.0, 1.0); - ASSERT_NEAR(roughness, 0.0, 0.0001); + ASSERT_EQ(size, fps); + ASSERT_NEAR(duration.InMillisecondsF(), 1000.0, 1.0); + ASSERT_NEAR(roughness, 0.0, 0.1); call_count++; }); int frames_to_run = - VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit * fps / 2 + 1; + VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit * fps + 1; NormalRun(fps, 60, {1}, frames_to_run); EXPECT_EQ(call_count, 1); } @@ -162,13 +229,13 @@ TEST_F(VideoPlaybackRoughnessReporterTest, BestCase50fps) { int fps = 50; SetReportingCallabck( [&](int size, base::TimeDelta duration, double roughness) { - ASSERT_EQ(size, fps / 2); - ASSERT_NEAR(duration.InMillisecondsF(), 500.0, 1.0); - ASSERT_NEAR(roughness, 0.0163, 0.0001); + ASSERT_EQ(size, fps); + ASSERT_NEAR(duration.InMillisecondsF(), 1000.0, 1.0); + ASSERT_NEAR(roughness, 8.1, 01); call_count++; }); int frames_to_run = - VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit * fps / 2 + 1; + VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit * fps + 1; NormalRun(fps, 60, {1, 1, 1, 1, 2}, frames_to_run); EXPECT_EQ(call_count, 1); } @@ -177,19 +244,18 @@ TEST_F(VideoPlaybackRoughnessReporterTest, BestCase50fps) { // get any result we need. TEST_F(VideoPlaybackRoughnessReporterTest, PredictableRoughnessValue) { int fps = 12; - int frames_in_window = fps / 2; + int frames_in_window = fps; int call_count = 0; - double intended_roughness = 0.042; + double intended_roughness = 4.2; base::TimeDelta vsync = base::TimeDelta::FromSecondsD(1.0 / fps); // Calculating the error value that needs to be injected into one frame // in order to get desired roughness. - base::TimeDelta error = - std::sqrt(intended_roughness * intended_roughness * frames_in_window) * - frames_in_window * vsync; + base::TimeDelta error = base::TimeDelta::FromMillisecondsD( + std::sqrt(intended_roughness * intended_roughness * frames_in_window)); auto callback = [&](int size, base::TimeDelta duration, double roughness) { ASSERT_EQ(frames_in_window, size); - ASSERT_NEAR(roughness, intended_roughness, 0.0001); + ASSERT_NEAR(roughness, intended_roughness, 0.1); call_count++; }; SetReportingCallabck(callback); @@ -204,7 +270,7 @@ TEST_F(VideoPlaybackRoughnessReporterTest, PredictableRoughnessValue) { auto frame = MakeFrame(vsync); reporter()->FrameSubmitted(token, *frame, vsync); - reporter()->FramePresented(token++, time); + reporter()->FramePresented(token++, time, true); reporter()->ProcessFrameWindow(); } } @@ -216,22 +282,22 @@ TEST_F(VideoPlaybackRoughnessReporterTest, PredictableRoughnessValue) { TEST_F(VideoPlaybackRoughnessReporterTest, TakingPercentile) { int token = 0; int fps = 12; - int frames_in_window = fps / 2; + int frames_in_window = fps; int call_count = 0; int win_count = 100; base::TimeDelta vsync = base::TimeDelta::FromSecondsD(1.0 / fps); std::vector<double> targets; targets.reserve(win_count); for (int i = 0; i < win_count; i++) - targets.push_back(i * 0.0001); + targets.push_back(i * 0.1); double expected_roughness = - VideoPlaybackRoughnessReporter::kPercentileToSubmit * 0.0001; + VideoPlaybackRoughnessReporter::kPercentileToSubmit * 0.1; std::mt19937 rnd(1); std::shuffle(targets.begin(), targets.end(), rnd); auto callback = [&](int size, base::TimeDelta duration, double roughness) { ASSERT_EQ(frames_in_window, size); - ASSERT_NEAR(roughness, expected_roughness, 0.00001); + ASSERT_NEAR(roughness, expected_roughness, 0.05); call_count++; }; SetReportingCallabck(callback); @@ -240,9 +306,8 @@ TEST_F(VideoPlaybackRoughnessReporterTest, TakingPercentile) { double roughness = targets[win_idx]; // Calculating the error value that needs to be injected into one frame // in order to get desired roughness. - base::TimeDelta error = - std::sqrt(roughness * roughness * frames_in_window) * frames_in_window * - vsync; + base::TimeDelta error = base::TimeDelta::FromMillisecondsD( + std::sqrt(roughness * roughness * frames_in_window)); for (int frame_idx = 0; frame_idx < frames_in_window; frame_idx++) { base::TimeTicks time; @@ -252,7 +317,7 @@ TEST_F(VideoPlaybackRoughnessReporterTest, TakingPercentile) { auto frame = MakeFrame(vsync); reporter()->FrameSubmitted(token, *frame, vsync); - reporter()->FramePresented(token++, time); + reporter()->FramePresented(token++, time, true); reporter()->ProcessFrameWindow(); } } @@ -271,7 +336,7 @@ TEST_F(VideoPlaybackRoughnessReporterTest, LongRunWithoutWindows) { auto frame = MakeFrame(vsync); reporter()->FrameSubmitted(i, *frame, vsync); if (i % 2 == 0) - reporter()->FramePresented(i, base::TimeTicks() + i * vsync); + reporter()->FramePresented(i, base::TimeTicks() + i * vsync, true); reporter()->ProcessFrameWindow(); ASSERT_TRUE(CheckSizes()); } @@ -288,7 +353,24 @@ TEST_F(VideoPlaybackRoughnessReporterTest, PresentingUnknownFrames) { for (int i = 0; i < 10000; i++) { auto frame = MakeFrame(vsync); reporter()->FrameSubmitted(i, *frame, vsync); - reporter()->FramePresented(i + 100000, base::TimeTicks() + i * vsync); + reporter()->FramePresented(i + 100000, base::TimeTicks() + i * vsync, true); + reporter()->ProcessFrameWindow(); + ASSERT_TRUE(CheckSizes()); + } + EXPECT_EQ(call_count, 0); +} + +// Test that the reporter is ignoring frames with unreliable +// presentation timestamp. +TEST_F(VideoPlaybackRoughnessReporterTest, IgnoringUnreliableTimings) { + int call_count = 0; + base::TimeDelta vsync = base::TimeDelta::FromMilliseconds(1); + SetReportingCallabck([&](int size, base::TimeDelta duration, + double roughness) { call_count++; }); + for (int i = 0; i < 10000; i++) { + auto frame = MakeFrame(vsync); + reporter()->FrameSubmitted(i, *frame, vsync); + reporter()->FramePresented(i, base::TimeTicks() + i * vsync, false); reporter()->ProcessFrameWindow(); ASSERT_TRUE(CheckSizes()); } @@ -307,7 +389,7 @@ TEST_F(VideoPlaybackRoughnessReporterTest, ReportingInReset) { // Set number of frames insufficient for reporting in Reset() int frames_to_run = - VideoPlaybackRoughnessReporter::kMinWindowsBeforeSubmit * fps / 2 - 1; + VideoPlaybackRoughnessReporter::kMinWindowsBeforeSubmit * fps - 1; NormalRun(fps, 60, {1}, frames_to_run); // No calls since, not enough windows were reported EXPECT_EQ(call_count, 0); @@ -318,7 +400,7 @@ TEST_F(VideoPlaybackRoughnessReporterTest, ReportingInReset) { // Set number of frames sufficient for reporting in Reset() frames_to_run = - VideoPlaybackRoughnessReporter::kMinWindowsBeforeSubmit * fps / 2 + 1; + VideoPlaybackRoughnessReporter::kMinWindowsBeforeSubmit * fps + 1; NormalRun(fps, 60, {1}, frames_to_run); // No calls since, not enough windows were reported @@ -338,27 +420,27 @@ TEST_F(VideoPlaybackRoughnessReporterTest, BatchPresentation) { // Try 60 fps SetReportingCallabck( [&](int size, base::TimeDelta duration, double roughness) { - ASSERT_EQ(size, fps / 2); - ASSERT_NEAR(duration.InMillisecondsF(), 500.0, 1.0); - ASSERT_NEAR(roughness, 0.0, 0.0001); + ASSERT_EQ(size, fps); + ASSERT_NEAR(duration.InMillisecondsF(), 1000.0, 1.0); + ASSERT_NEAR(roughness, 0.0, 0.1); call_count++; }); int frames_to_run = - VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit * fps / 2 + 10; + VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit * fps + 10; BatchPresentationRun(fps, 60, {1}, frames_to_run); EXPECT_EQ(call_count, 1); // Try 24fps SetReportingCallabck( [&](int size, base::TimeDelta duration, double roughness) { - ASSERT_EQ(size, fps / 2); - ASSERT_NEAR(duration.InMillisecondsF(), 500.0, 1.0); - ASSERT_NEAR(roughness, 0.0118, 0.0001); + ASSERT_EQ(size, fps); + ASSERT_NEAR(duration.InMillisecondsF(), 1000.0, 1.0); + ASSERT_NEAR(roughness, 5.9, 0.1); call_count++; }); fps = 24; frames_to_run = - VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit * fps / 2 + 10; + VideoPlaybackRoughnessReporter::kMaxWindowsBeforeSubmit * fps + 10; BatchPresentationRun(fps, 60, {2, 3}, frames_to_run); EXPECT_EQ(call_count, 2); } diff --git a/chromium/cc/paint/BUILD.gn b/chromium/cc/paint/BUILD.gn index 4c3776c0411..0a9161d4e66 100644 --- a/chromium/cc/paint/BUILD.gn +++ b/chromium/cc/paint/BUILD.gn @@ -107,6 +107,7 @@ cc_component("paint") { deps = [ "//base", + "//gpu/command_buffer/common:mailbox", "//ui/gfx/animation", "//ui/gfx/ipc/color", ] diff --git a/chromium/cc/paint/decoded_draw_image.cc b/chromium/cc/paint/decoded_draw_image.cc index 2c66554d449..69f195f2bb4 100644 --- a/chromium/cc/paint/decoded_draw_image.cc +++ b/chromium/cc/paint/decoded_draw_image.cc @@ -17,6 +17,14 @@ DecodedDrawImage::DecodedDrawImage(sk_sp<const SkImage> image, filter_quality_(filter_quality), is_budgeted_(is_budgeted) {} +DecodedDrawImage::DecodedDrawImage(const gpu::Mailbox& mailbox, + SkFilterQuality filter_quality) + : mailbox_(mailbox), + src_rect_offset_(SkSize::MakeEmpty()), + scale_adjustment_(SkSize::Make(1.f, 1.f)), + filter_quality_(filter_quality), + is_budgeted_(true) {} + DecodedDrawImage::DecodedDrawImage( base::Optional<uint32_t> transfer_cache_entry_id, const SkSize& src_rect_offset, diff --git a/chromium/cc/paint/decoded_draw_image.h b/chromium/cc/paint/decoded_draw_image.h index 02b3ad1092c..a3e47d30fc0 100644 --- a/chromium/cc/paint/decoded_draw_image.h +++ b/chromium/cc/paint/decoded_draw_image.h @@ -10,6 +10,7 @@ #include "base/optional.h" #include "cc/paint/paint_export.h" +#include "gpu/command_buffer/common/mailbox.h" #include "third_party/skia/include/core/SkFilterQuality.h" #include "third_party/skia/include/core/SkImage.h" #include "third_party/skia/include/core/SkRefCnt.h" @@ -29,6 +30,7 @@ class CC_PAINT_EXPORT DecodedDrawImage { const SkSize& scale_adjustment, SkFilterQuality filter_quality, bool is_budgeted); + DecodedDrawImage(const gpu::Mailbox& mailbox, SkFilterQuality filter_quality); DecodedDrawImage(base::Optional<uint32_t> transfer_cache_entry_id, const SkSize& src_rect_offset, const SkSize& scale_adjustment, @@ -62,6 +64,7 @@ class CC_PAINT_EXPORT DecodedDrawImage { private: sk_sp<const SkImage> image_; + gpu::Mailbox mailbox_; base::Optional<uint32_t> transfer_cache_entry_id_; SkSize src_rect_offset_; SkSize scale_adjustment_; diff --git a/chromium/cc/paint/discardable_image_map_unittest.cc b/chromium/cc/paint/discardable_image_map_unittest.cc index fd52a353fcc..380ee6d6ae2 100644 --- a/chromium/cc/paint/discardable_image_map_unittest.cc +++ b/chromium/cc/paint/discardable_image_map_unittest.cc @@ -626,8 +626,8 @@ TEST_F(DiscardableImageMapTest, GetDiscardableImagesInShader) { .set_paint_image_generator( CreatePaintImageGenerator(gfx::Size(500, 500))) .TakePaintImage(); - SkMatrix scale = SkMatrix::MakeScale(std::max(x * 0.5f, kMinScale), - std::max(y * 0.5f, kMinScale)); + SkMatrix scale = SkMatrix::Scale(std::max(x * 0.5f, kMinScale), + std::max(y * 0.5f, kMinScale)); PaintFlags flags; flags.setShader(PaintShader::MakeImage(discardable_image[y][x], SkTileMode::kClamp, diff --git a/chromium/cc/paint/display_item_list.cc b/chromium/cc/paint/display_item_list.cc index 52a3aac9f07..071f57b646f 100644 --- a/chromium/cc/paint/display_item_list.cc +++ b/chromium/cc/paint/display_item_list.cc @@ -296,7 +296,6 @@ void DisplayItemList::Reset() { offsets_.shrink_to_fit(); paired_begin_stack_.clear(); paired_begin_stack_.shrink_to_fit(); - has_draw_ops_ = false; } sk_sp<PaintRecord> DisplayItemList::ReleaseAsRecord() { diff --git a/chromium/cc/paint/display_item_list.h b/chromium/cc/paint/display_item_list.h index b518d67715a..c065c33e60a 100644 --- a/chromium/cc/paint/display_item_list.h +++ b/chromium/cc/paint/display_item_list.h @@ -91,8 +91,6 @@ class CC_PAINT_EXPORT DisplayItemList if (usage_hint_ == kTopLevelDisplayItemList) offsets_.push_back(offset); const T* op = paint_op_buffer_.push<T>(std::forward<Args>(args)...); - if (op->IsDrawOp()) - has_draw_ops_ = true; DCHECK(op->IsValid()); return offset; } @@ -181,11 +179,17 @@ class CC_PAINT_EXPORT DisplayItemList int max_ops_to_analyze = 1); std::string ToString() const; - bool has_draw_ops() const { return has_draw_ops_; } + bool has_draw_ops() const { return paint_op_buffer_.has_draw_ops(); } // Ops with nested paint ops are considered as a single op. size_t num_paint_ops() const { return paint_op_buffer_.size(); } + bool NeedsAdditionalInvalidationForLCDText( + const DisplayItemList& old_list) const { + return paint_op_buffer_.NeedsAdditionalInvalidationForLCDText( + old_list.paint_op_buffer_); + } + private: friend class DisplayItemListTest; friend gpu::raster::RasterImplementation; @@ -238,7 +242,6 @@ class CC_PAINT_EXPORT DisplayItemList #endif UsageHint usage_hint_; - bool has_draw_ops_ = false; friend class base::RefCountedThreadSafe<DisplayItemList>; FRIEND_TEST_ALL_PREFIXES(DisplayItemListTest, BytesUsed); diff --git a/chromium/cc/paint/display_item_list_unittest.cc b/chromium/cc/paint/display_item_list_unittest.cc index a4dea57bbfb..a8230788bbb 100644 --- a/chromium/cc/paint/display_item_list_unittest.cc +++ b/chromium/cc/paint/display_item_list_unittest.cc @@ -8,6 +8,7 @@ #include <vector> +#include "base/logging.h" #include "base/trace_event/traced_value.h" #include "base/values.h" #include "cc/paint/filter_operation.h" diff --git a/chromium/cc/paint/element_id.cc b/chromium/cc/paint/element_id.cc index 3465eb1117a..a5b11cb2da2 100644 --- a/chromium/cc/paint/element_id.cc +++ b/chromium/cc/paint/element_id.cc @@ -15,6 +15,11 @@ namespace cc { const ElementIdType ElementId::kInvalidElementId = 0; +// static +bool ElementId::IsValid(ElementIdType id) { + return id != kInvalidElementId; +} + ElementId LayerIdToElementIdForTesting(int layer_id) { return ElementId(std::numeric_limits<int>::max() - layer_id); } diff --git a/chromium/cc/paint/element_id.h b/chromium/cc/paint/element_id.h index a4108a13b37..fcdebf565b7 100644 --- a/chromium/cc/paint/element_id.h +++ b/chromium/cc/paint/element_id.h @@ -63,6 +63,8 @@ struct CC_PAINT_EXPORT ElementId { std::string ToString() const; + static bool IsValid(ElementIdType id); + private: friend struct ElementIdHash; static const ElementIdType kInvalidElementId; diff --git a/chromium/cc/paint/filter_operation.h b/chromium/cc/paint/filter_operation.h index e1c018b9be3..8971192195f 100644 --- a/chromium/cc/paint/filter_operation.h +++ b/chromium/cc/paint/filter_operation.h @@ -8,7 +8,7 @@ #include <memory> #include <vector> -#include "base/logging.h" +#include "base/check_op.h" #include "cc/paint/paint_export.h" #include "cc/paint/paint_filter.h" #include "third_party/skia/include/core/SkColor.h" diff --git a/chromium/cc/paint/filter_operations.h b/chromium/cc/paint/filter_operations.h index 5bc51ad8ec8..1186796cd1a 100644 --- a/chromium/cc/paint/filter_operations.h +++ b/chromium/cc/paint/filter_operations.h @@ -10,7 +10,7 @@ #include <string> #include <vector> -#include "base/logging.h" +#include "base/check_op.h" #include "cc/paint/filter_operation.h" namespace base { diff --git a/chromium/cc/paint/filter_operations_unittest.cc b/chromium/cc/paint/filter_operations_unittest.cc index 93b3dbb0eed..7e4fdc4834d 100644 --- a/chromium/cc/paint/filter_operations_unittest.cc +++ b/chromium/cc/paint/filter_operations_unittest.cc @@ -23,9 +23,9 @@ TEST(FilterOperationsTest, MapRectBlur) { EXPECT_EQ(gfx::Rect(-60, -60, 130, 130), ops.MapRect(gfx::Rect(0, 0, 10, 10), SkMatrix::I())); EXPECT_EQ(gfx::Rect(-120, -120, 260, 260), - ops.MapRect(gfx::Rect(0, 0, 20, 20), SkMatrix::MakeScale(2, 2))); + ops.MapRect(gfx::Rect(0, 0, 20, 20), SkMatrix::Scale(2, 2))); EXPECT_EQ(gfx::Rect(-60, -70, 130, 130), - ops.MapRect(gfx::Rect(0, -10, 10, 10), SkMatrix::MakeScale(1, -1))); + ops.MapRect(gfx::Rect(0, -10, 10, 10), SkMatrix::Scale(1, -1))); } TEST(FilterOperationsTest, MapRectBlurOverflow) { @@ -43,10 +43,10 @@ TEST(FilterOperationsTest, MapRectReverseBlur) { ops.MapRectReverse(gfx::Rect(0, 0, 150, 150), SkMatrix::I())); EXPECT_EQ( gfx::Rect(120, 120, 60, 60), - ops.MapRectReverse(gfx::Rect(0, 0, 300, 300), SkMatrix::MakeScale(2, 2))); - EXPECT_EQ(gfx::Rect(60, 50, 30, 30), - ops.MapRectReverse(gfx::Rect(0, -10, 150, 150), - SkMatrix::MakeScale(1, -1))); + ops.MapRectReverse(gfx::Rect(0, 0, 300, 300), SkMatrix::Scale(2, 2))); + EXPECT_EQ( + gfx::Rect(60, 50, 30, 30), + ops.MapRectReverse(gfx::Rect(0, -10, 150, 150), SkMatrix::Scale(1, -1))); } TEST(FilterOperationsTest, MapRectDropShadowReferenceFilter) { @@ -60,9 +60,9 @@ TEST(FilterOperationsTest, MapRectDropShadowReferenceFilter) { EXPECT_EQ(gfx::Rect(-9, -19, 34, 64), ops.MapRect(gfx::Rect(0, 0, 10, 10), SkMatrix::I())); EXPECT_EQ(gfx::Rect(-18, -38, 68, 128), - ops.MapRect(gfx::Rect(0, 0, 20, 20), SkMatrix::MakeScale(2, 2))); + ops.MapRect(gfx::Rect(0, 0, 20, 20), SkMatrix::Scale(2, 2))); EXPECT_EQ(gfx::Rect(-9, -45, 34, 64), - ops.MapRect(gfx::Rect(0, -10, 10, 10), SkMatrix::MakeScale(1, -1))); + ops.MapRect(gfx::Rect(0, -10, 10, 10), SkMatrix::Scale(1, -1))); } TEST(FilterOperationsTest, MapRectReverseDropShadowReferenceFilter) { @@ -75,12 +75,11 @@ TEST(FilterOperationsTest, MapRectReverseDropShadowReferenceFilter) { nullptr))); EXPECT_EQ(gfx::Rect(-15, -35, 34, 64), ops.MapRectReverse(gfx::Rect(0, 0, 10, 10), SkMatrix::I())); + EXPECT_EQ(gfx::Rect(-30, -70, 68, 128), + ops.MapRectReverse(gfx::Rect(0, 0, 20, 20), SkMatrix::Scale(2, 2))); EXPECT_EQ( - gfx::Rect(-30, -70, 68, 128), - ops.MapRectReverse(gfx::Rect(0, 0, 20, 20), SkMatrix::MakeScale(2, 2))); - EXPECT_EQ(gfx::Rect(-15, -29, 34, 64), - ops.MapRectReverse(gfx::Rect(0, -10, 10, 10), - SkMatrix::MakeScale(1, -1))); + gfx::Rect(-15, -29, 34, 64), + ops.MapRectReverse(gfx::Rect(0, -10, 10, 10), SkMatrix::Scale(1, -1))); } TEST(FilterOperationsTest, MapRectOffsetReferenceFilter) { @@ -90,9 +89,9 @@ TEST(FilterOperationsTest, MapRectOffsetReferenceFilter) { EXPECT_EQ(gfx::Rect(30, 40, 10, 10), ops.MapRect(gfx::Rect(0, 0, 10, 10), SkMatrix::I())); EXPECT_EQ(gfx::Rect(60, 80, 20, 20), - ops.MapRect(gfx::Rect(0, 0, 20, 20), SkMatrix::MakeScale(2, 2))); + ops.MapRect(gfx::Rect(0, 0, 20, 20), SkMatrix::Scale(2, 2))); EXPECT_EQ(gfx::Rect(30, -50, 10, 10), - ops.MapRect(gfx::Rect(0, -10, 10, 10), SkMatrix::MakeScale(1, -1))); + ops.MapRect(gfx::Rect(0, -10, 10, 10), SkMatrix::Scale(1, -1))); } TEST(FilterOperationsTest, MapRectReverseOffsetReferenceFilter) { @@ -101,12 +100,11 @@ TEST(FilterOperationsTest, MapRectReverseOffsetReferenceFilter) { ops.Append(FilterOperation::CreateReferenceFilter(std::move(filter))); EXPECT_EQ(gfx::Rect(-30, -40, 10, 10), ops.MapRectReverse(gfx::Rect(0, 0, 10, 10), SkMatrix::I())); + EXPECT_EQ(gfx::Rect(-60, -80, 20, 20), + ops.MapRectReverse(gfx::Rect(0, 0, 20, 20), SkMatrix::Scale(2, 2))); EXPECT_EQ( - gfx::Rect(-60, -80, 20, 20), - ops.MapRectReverse(gfx::Rect(0, 0, 20, 20), SkMatrix::MakeScale(2, 2))); - EXPECT_EQ(gfx::Rect(-30, 30, 10, 10), - ops.MapRectReverse(gfx::Rect(0, -10, 10, 10), - SkMatrix::MakeScale(1, -1))); + gfx::Rect(-30, 30, 10, 10), + ops.MapRectReverse(gfx::Rect(0, -10, 10, 10), SkMatrix::Scale(1, -1))); } TEST(FilterOperationsTest, MapRectCombineNonCommutative) { @@ -123,9 +121,9 @@ TEST(FilterOperationsTest, MapRectCombineNonCommutative) { EXPECT_EQ(gfx::Rect(200, 200, 20, 20), ops.MapRect(gfx::Rect(10, 10), SkMatrix::I())); EXPECT_EQ(gfx::Rect(400, 400, 40, 40), - ops.MapRect(gfx::Rect(20, 20), SkMatrix::MakeScale(2, 2))); + ops.MapRect(gfx::Rect(20, 20), SkMatrix::Scale(2, 2))); EXPECT_EQ(gfx::Rect(200, -220, 20, 20), - ops.MapRect(gfx::Rect(0, -10, 10, 10), SkMatrix::MakeScale(1, -1))); + ops.MapRect(gfx::Rect(0, -10, 10, 10), SkMatrix::Scale(1, -1))); } TEST(FilterOperationsTest, MapRectReverseCombineNonCommutative) { @@ -142,10 +140,10 @@ TEST(FilterOperationsTest, MapRectReverseCombineNonCommutative) { EXPECT_EQ(gfx::Rect(10, 10), ops.MapRectReverse(gfx::Rect(200, 200, 20, 20), SkMatrix::I())); EXPECT_EQ(gfx::Rect(20, 20), ops.MapRectReverse(gfx::Rect(400, 400, 40, 40), - SkMatrix::MakeScale(2, 2))); - EXPECT_EQ(gfx::Rect(0, -10, 10, 10), - ops.MapRectReverse(gfx::Rect(200, -220, 20, 20), - SkMatrix::MakeScale(1, -1))); + SkMatrix::Scale(2, 2))); + EXPECT_EQ( + gfx::Rect(0, -10, 10, 10), + ops.MapRectReverse(gfx::Rect(200, -220, 20, 20), SkMatrix::Scale(1, -1))); } TEST(FilterOperationsTest, MapRectNullReferenceFilter) { @@ -154,9 +152,9 @@ TEST(FilterOperationsTest, MapRectNullReferenceFilter) { EXPECT_EQ(gfx::Rect(0, 0, 10, 10), ops.MapRect(gfx::Rect(0, 0, 10, 10), SkMatrix::I())); EXPECT_EQ(gfx::Rect(0, 0, 20, 20), - ops.MapRect(gfx::Rect(0, 0, 20, 20), SkMatrix::MakeScale(2, 2))); + ops.MapRect(gfx::Rect(0, 0, 20, 20), SkMatrix::Scale(2, 2))); EXPECT_EQ(gfx::Rect(0, -10, 10, 10), - ops.MapRect(gfx::Rect(0, -10, 10, 10), SkMatrix::MakeScale(1, -1))); + ops.MapRect(gfx::Rect(0, -10, 10, 10), SkMatrix::Scale(1, -1))); } TEST(FilterOperationsTest, MapRectReverseNullReferenceFilter) { @@ -164,12 +162,11 @@ TEST(FilterOperationsTest, MapRectReverseNullReferenceFilter) { ops.Append(FilterOperation::CreateReferenceFilter(nullptr)); EXPECT_EQ(gfx::Rect(0, 0, 10, 10), ops.MapRectReverse(gfx::Rect(0, 0, 10, 10), SkMatrix::I())); + EXPECT_EQ(gfx::Rect(0, 0, 20, 20), + ops.MapRectReverse(gfx::Rect(0, 0, 20, 20), SkMatrix::Scale(2, 2))); EXPECT_EQ( - gfx::Rect(0, 0, 20, 20), - ops.MapRectReverse(gfx::Rect(0, 0, 20, 20), SkMatrix::MakeScale(2, 2))); - EXPECT_EQ(gfx::Rect(0, -10, 10, 10), - ops.MapRectReverse(gfx::Rect(0, -10, 10, 10), - SkMatrix::MakeScale(1, -1))); + gfx::Rect(0, -10, 10, 10), + ops.MapRectReverse(gfx::Rect(0, -10, 10, 10), SkMatrix::Scale(1, -1))); } TEST(FilterOperationsTest, MapRectDropShadow) { @@ -178,9 +175,9 @@ TEST(FilterOperationsTest, MapRectDropShadow) { EXPECT_EQ(gfx::Rect(-57, -52, 130, 130), ops.MapRect(gfx::Rect(0, 0, 10, 10), SkMatrix::I())); EXPECT_EQ(gfx::Rect(-114, -104, 260, 260), - ops.MapRect(gfx::Rect(0, 0, 20, 20), SkMatrix::MakeScale(2, 2))); + ops.MapRect(gfx::Rect(0, 0, 20, 20), SkMatrix::Scale(2, 2))); EXPECT_EQ(gfx::Rect(-57, -78, 130, 130), - ops.MapRect(gfx::Rect(0, -10, 10, 10), SkMatrix::MakeScale(1, -1))); + ops.MapRect(gfx::Rect(0, -10, 10, 10), SkMatrix::Scale(1, -1))); } TEST(FilterOperationsTest, MapRectReverseDropShadow) { @@ -188,12 +185,11 @@ TEST(FilterOperationsTest, MapRectReverseDropShadow) { ops.Append(FilterOperation::CreateDropShadowFilter(gfx::Point(3, 8), 20, 0)); EXPECT_EQ(gfx::Rect(-63, -68, 130, 130), ops.MapRectReverse(gfx::Rect(0, 0, 10, 10), SkMatrix::I())); + EXPECT_EQ(gfx::Rect(-126, -136, 260, 260), + ops.MapRectReverse(gfx::Rect(0, 0, 20, 20), SkMatrix::Scale(2, 2))); EXPECT_EQ( - gfx::Rect(-126, -136, 260, 260), - ops.MapRectReverse(gfx::Rect(0, 0, 20, 20), SkMatrix::MakeScale(2, 2))); - EXPECT_EQ(gfx::Rect(-63, -62, 130, 130), - ops.MapRectReverse(gfx::Rect(0, -10, 10, 10), - SkMatrix::MakeScale(1, -1))); + gfx::Rect(-63, -62, 130, 130), + ops.MapRectReverse(gfx::Rect(0, -10, 10, 10), SkMatrix::Scale(1, -1))); } TEST(FilterOperationsTest, MapRectDropShadowDoesNotContract) { diff --git a/chromium/cc/paint/image_transfer_cache_entry.cc b/chromium/cc/paint/image_transfer_cache_entry.cc index 155d95f1e3b..1f669ebefb4 100644 --- a/chromium/cc/paint/image_transfer_cache_entry.cc +++ b/chromium/cc/paint/image_transfer_cache_entry.cc @@ -24,6 +24,14 @@ namespace cc { namespace { +struct Context { + const std::vector<sk_sp<SkImage>> sk_planes_; +}; + +void ReleaseContext(SkImage::ReleaseContext context) { + auto* texture_context = static_cast<Context*>(context); + delete texture_context; +} // Creates a SkImage backed by the YUV textures corresponding to |plane_images|. // The layout is specified by |plane_images_format|). The backend textures are @@ -81,10 +89,11 @@ sk_sp<SkImage> MakeYUVImageFromUploadedPlanes( return nullptr; } plane_indices[SkYUVAIndex::kA_Index] = {-1, SkColorChannel::kR}; + Context* ctx = new Context{plane_images}; sk_sp<SkImage> image = SkImage::MakeFromYUVATextures( context, yuv_color_space, plane_backend_textures.data(), plane_indices, plane_images[0]->dimensions(), kTopLeft_GrSurfaceOrigin, - std::move(image_color_space)); + std::move(image_color_space), ReleaseContext, ctx); if (!image) { DLOG(ERROR) << "Could not create YUV image"; return nullptr; diff --git a/chromium/cc/paint/image_transfer_cache_entry_unittest.cc b/chromium/cc/paint/image_transfer_cache_entry_unittest.cc index 24a334eedbb..0da5db2f946 100644 --- a/chromium/cc/paint/image_transfer_cache_entry_unittest.cc +++ b/chromium/cc/paint/image_transfer_cache_entry_unittest.cc @@ -144,7 +144,7 @@ class ImageTransferCacheEntryTest if (texture.isValid()) gr_context_->deleteBackendTexture(texture); } - gr_context_->flush(); + gr_context_->flushAndSubmit(); textures_to_free_.clear(); } diff --git a/chromium/cc/paint/oop_pixeltest.cc b/chromium/cc/paint/oop_pixeltest.cc index 6c9f1c1e192..ffc090ebee1 100644 --- a/chromium/cc/paint/oop_pixeltest.cc +++ b/chromium/cc/paint/oop_pixeltest.cc @@ -332,7 +332,7 @@ class OopPixelTest : public testing::Test, raster_source->PlaybackToCanvas( canvas, options.content_size, options.full_raster_rect, options.playback_rect, raster_transform, settings); - surface->flush(); + surface->flushAndSubmit(); EXPECT_EQ(gles2_context_provider_->ContextGL()->GetError(), static_cast<unsigned>(GL_NO_ERROR)); @@ -852,7 +852,7 @@ TEST_P(OopImagePixelTest, DrawImageWithSetMatrix) { display_item_list->StartPaint(); PaintFlags flags; flags.setFilterQuality(FilterQuality()); - display_item_list->push<SetMatrixOp>(SkMatrix::MakeScale(0.5f, 0.5f)); + display_item_list->push<SetMatrixOp>(SkMatrix::Scale(0.5f, 0.5f)); display_item_list->push<DrawImageOp>(paint_image, 0.f, 0.f, &flags); display_item_list->EndPaintOfUnpaired(rect); display_item_list->Finalize(); @@ -1358,6 +1358,39 @@ TEST_F(OopPixelTest, DrawRectScaleTransformOptions) { ExpectEquals(actual, expected); } +TEST_F(OopPixelTest, DrawRectTransformOptionsFullRaster) { + PaintFlags flags; + // Use powers of two here to make floating point blending consistent. + flags.setColor(SkColorSetRGB(64, 128, 32)); + flags.setAntiAlias(true); + gfx::Rect draw_rect(0, 0, 19, 19); + + auto display_item_list = base::MakeRefCounted<DisplayItemList>(); + display_item_list->StartPaint(); + display_item_list->push<DrawRectOp>(gfx::RectToSkRect(draw_rect), flags); + display_item_list->EndPaintOfUnpaired(draw_rect); + display_item_list->Finalize(); + + // The opaque rect above is 1px smaller than the canvas. With the subpixel + // translation, the rect fills the whole canvas, but the pixels at the edges + // are translucent. We should clear the canvas before drawing the rect, so + // the translucent pixels at the edges should not expose the preclear color, + // even if requires_clear is not true. + RasterOptions options; + options.resource_size = {20, 20}; + options.content_size = {25, 25}; + options.full_raster_rect = {5, 5, 20, 20}; + options.playback_rect = {5, 5, 20, 20}; + options.preclear = true; + options.preclear_color = SK_ColorRED; + options.post_translate = {0.5f, 0.25f}; + options.post_scale = 2.f; + + auto actual = Raster(display_item_list, options); + auto expected = RasterExpectedBitmap(display_item_list, options); + ExpectEquals(actual, expected); +} + TEST_F(OopPixelTest, DrawRectQueryMiddleOfDisplayList) { auto display_item_list = base::MakeRefCounted<DisplayItemList>(); std::vector<SkColor> colors = { @@ -1546,12 +1579,12 @@ class OopRecordFilterPixelTest : public OopPixelTest, }; TEST_P(OopRecordFilterPixelTest, FilterWithTextScaled) { - SkMatrix mat = SkMatrix::MakeScale(2.f, 2.f); + SkMatrix mat = SkMatrix::Scale(2.f, 2.f); RunTest(mat); } TEST_P(OopRecordFilterPixelTest, FilterWithTextAndComplexCTM) { - SkMatrix mat = SkMatrix::MakeScale(2.f, 2.f); + SkMatrix mat = SkMatrix::Scale(2.f, 2.f); mat.preSkew(2.f, 2.f); RunTest(mat); } diff --git a/chromium/cc/paint/paint_canvas.h b/chromium/cc/paint/paint_canvas.h index 8a0883b7fc8..2b9c34a2336 100644 --- a/chromium/cc/paint/paint_canvas.h +++ b/chromium/cc/paint/paint_canvas.h @@ -146,16 +146,11 @@ class CC_PAINT_EXPORT PaintCanvas { drawImage(image, left, top, nullptr); } - enum SrcRectConstraint { - kStrict_SrcRectConstraint = SkCanvas::kStrict_SrcRectConstraint, - kFast_SrcRectConstraint = SkCanvas::kFast_SrcRectConstraint, - }; - virtual void drawImageRect(const PaintImage& image, const SkRect& src, const SkRect& dst, const PaintFlags* flags, - SrcRectConstraint constraint) = 0; + SkCanvas::SrcRectConstraint constraint) = 0; // Draws the frame of the |skottie| animation specified by the normalized time // t [0->first frame..1->last frame] at the destination bounds given by |dst| diff --git a/chromium/cc/paint/paint_filter.cc b/chromium/cc/paint/paint_filter.cc index 279258b26b7..cf868e21167 100644 --- a/chromium/cc/paint/paint_filter.cc +++ b/chromium/cc/paint/paint_filter.cc @@ -701,7 +701,7 @@ sk_sp<PaintFilter> ImagePaintFilter::SnapshotWithImagesInternal( PaintImage decoded_paint_image = PaintImageBuilder::WithDefault() .set_id(image_.stable_id()) - .set_image(decoded_sk_image, PaintImage::GetNextContentId()) + .set_texture_image(decoded_sk_image, PaintImage::GetNextContentId()) .TakePaintImage(); return sk_make_sp<ImagePaintFilter>(std::move(decoded_paint_image), src_rect_, diff --git a/chromium/cc/paint/paint_filter.h b/chromium/cc/paint/paint_filter.h index cec36ef7c6a..556ab9a91e8 100644 --- a/chromium/cc/paint/paint_filter.h +++ b/chromium/cc/paint/paint_filter.h @@ -5,8 +5,8 @@ #ifndef CC_PAINT_PAINT_FILTER_H_ #define CC_PAINT_PAINT_FILTER_H_ +#include "base/check_op.h" #include "base/containers/stack_container.h" -#include "base/logging.h" #include "base/optional.h" #include "base/stl_util.h" #include "cc/paint/paint_export.h" diff --git a/chromium/cc/paint/paint_flags.h b/chromium/cc/paint/paint_flags.h index c1f1cb5ce2f..e28e68929c9 100644 --- a/chromium/cc/paint/paint_flags.h +++ b/chromium/cc/paint/paint_flags.h @@ -33,7 +33,6 @@ class CC_PAINT_EXPORT PaintFlags { enum Style { kFill_Style = SkPaint::kFill_Style, kStroke_Style = SkPaint::kStroke_Style, - kStrokeAndFill_Style = SkPaint::kStrokeAndFill_Style, }; bool nothingToDraw() const; ALWAYS_INLINE Style getStyle() const { @@ -141,9 +140,13 @@ class CC_PAINT_EXPORT PaintFlags { draw_looper_ = std::move(looper); } - // Returns true if this just represents an opacity blend when - // used as saveLayer flags. + // Returns true if this just represents an opacity blend when used as + // saveLayer flags, thus the saveLayer can be converted to a saveLayerAlpha. bool IsSimpleOpacity() const; + + // Returns true if this (of a drawOp) allows the sequence + // saveLayerAlpha/drawOp/restore to be folded into a single drawOp by baking + // the alpha in the saveLayerAlpha into the flags of the drawOp. bool SupportsFoldingAlpha() const; // SkPaint does not support loopers, so callers of SkToPaint need diff --git a/chromium/cc/paint/paint_image.cc b/chromium/cc/paint/paint_image.cc index 86deeddd70f..529718556d9 100644 --- a/chromium/cc/paint/paint_image.cc +++ b/chromium/cc/paint/paint_image.cc @@ -5,6 +5,7 @@ #include "cc/paint/paint_image.h" #include <memory> +#include <sstream> #include "base/atomic_sequence_num.h" #include "base/hash/hash.h" @@ -113,6 +114,15 @@ const sk_sp<SkImage>& PaintImage::GetSkImage() const { return cached_sk_image_; } +const sk_sp<SkImage>& PaintImage::GetRasterSkImage() const { + return cached_sk_image_; +} + +gpu::Mailbox PaintImage::GetMailbox() const { + DCHECK(texture_backing_); + return texture_backing_->GetMailbox(); +} + PaintImage PaintImage::MakeSubset(const gfx::Rect& subset) const { DCHECK(!subset.IsEmpty()); @@ -133,7 +143,7 @@ PaintImage PaintImage::MakeSubset(const gfx::Rect& subset) const { // PaintImage is an optimization to allow re-use of the original decode for // image subsets in skia, for cases that rely on skia's image decode cache. result.cached_sk_image_ = - GetSkImage()->makeSubset(gfx::RectToSkIRect(subset)); + GetRasterSkImage()->makeSubset(gfx::RectToSkIRect(subset)); return result; } @@ -288,6 +298,14 @@ SkAlphaType PaintImage::GetAlphaType() const { return kUnknown_SkAlphaType; } +bool PaintImage::IsTextureBacked() const { + if (texture_backing_) + return true; + if (cached_sk_image_) + return cached_sk_image_->isTextureBacked(); + return false; +} + int PaintImage::width() const { return paint_worklet_input_ ? static_cast<int>(paint_worklet_input_->GetSize().width()) @@ -361,18 +379,19 @@ sk_sp<SkImage> PaintImage::GetSkImageForFrame( size_t index, GeneratorClientId client_id) const { DCHECK_LT(index, FrameCount()); + DCHECK(!IsTextureBacked()); // |client_id| and |index| are only relevant for generator backed images which // perform lazy decoding and can be multi-frame. if (!paint_image_generator_) { DCHECK_EQ(index, kDefaultFrameIndex); - return GetSkImage(); + return GetRasterSkImage(); } // The internally cached SkImage is constructed using the default frame index // and GeneratorClientId. Avoid creating a new SkImage. if (index == kDefaultFrameIndex && client_id == kDefaultGeneratorClientId) - return GetSkImage(); + return GetRasterSkImage(); sk_sp<SkImage> image = SkImage::MakeFromGenerator(std::make_unique<SkiaPaintImageGenerator>( diff --git a/chromium/cc/paint/paint_image.h b/chromium/cc/paint/paint_image.h index 95881204e0a..1d7503a2d04 100644 --- a/chromium/cc/paint/paint_image.h +++ b/chromium/cc/paint/paint_image.h @@ -8,12 +8,12 @@ #include <vector> #include "base/gtest_prod_util.h" -#include "base/logging.h" #include "base/memory/scoped_refptr.h" #include "base/optional.h" #include "cc/paint/frame_metadata.h" #include "cc/paint/image_animation_count.h" #include "cc/paint/paint_export.h" +#include "gpu/command_buffer/common/mailbox.h" #include "third_party/skia/include/core/SkImage.h" #include "third_party/skia/include/core/SkYUVAIndex.h" #include "third_party/skia/include/core/SkYUVASizeInfo.h" @@ -229,8 +229,16 @@ class CC_PAINT_EXPORT PaintImage { const SkYUVASizeInfo& yuva_size_info, SkYUVAIndex* plane_indices) const; + // Returns the SkImage associated with this PaintImage. If PaintImage is + // texture backed, this API may do a readback from GPU to CPU memory. + // Avoid using this API unless actual pixels are needed. + // For other cases, prefer using PaintImage APIs directly or use + // GetSkImageInfo() for metadata about the SkImage. + const sk_sp<SkImage>& GetRasterSkImage() const; + Id stable_id() const { return id_; } const sk_sp<SkImage>& GetSkImage() const; + gpu::Mailbox GetMailbox() const; AnimationType animation_type() const { return animation_type_; } CompletionState completion_state() const { return completion_state_; } bool is_multipart() const { return is_multipart_; } @@ -253,9 +261,7 @@ class CC_PAINT_EXPORT PaintImage { return paint_worklet_input_ ? false : GetSkImage()->isLazyGenerated(); } bool IsPaintWorklet() const { return !!paint_worklet_input_; } - bool IsTextureBacked() const { - return paint_worklet_input_ ? false : GetSkImage()->isTextureBacked(); - } + bool IsTextureBacked() const; int width() const; int height() const; SkColorSpace* color_space() const { diff --git a/chromium/cc/paint/paint_image_builder.h b/chromium/cc/paint/paint_image_builder.h index 25a20bf1a9c..8820b078f5e 100644 --- a/chromium/cc/paint/paint_image_builder.h +++ b/chromium/cc/paint/paint_image_builder.h @@ -43,10 +43,17 @@ class CC_PAINT_EXPORT PaintImageBuilder { PaintImageBuilder&& set_image(sk_sp<SkImage> sk_image, PaintImage::ContentId content_id) { + DCHECK(!sk_image->isTextureBacked()); paint_image_.sk_image_ = std::move(sk_image); paint_image_.content_id_ = content_id; return std::move(*this); } + PaintImageBuilder&& set_texture_backing(sk_sp<TextureBacking> texture_backing, + PaintImage::ContentId content_id) { + paint_image_.texture_backing_ = std::move(texture_backing); + paint_image_.content_id_ = content_id; + return std::move(*this); + } PaintImageBuilder&& set_paint_record(sk_sp<PaintRecord> paint_record, const gfx::Rect& rect, PaintImage::ContentId content_id) { @@ -62,7 +69,6 @@ class CC_PAINT_EXPORT PaintImageBuilder { paint_image_.paint_image_generator_ = std::move(generator); return std::move(*this); } - PaintImageBuilder&& set_animation_type(PaintImage::AnimationType type) { paint_image_.animation_type_ = type; return std::move(*this); @@ -105,19 +111,26 @@ class CC_PAINT_EXPORT PaintImageBuilder { paint_image_.paint_worklet_input_ = std::move(input); return std::move(*this); } - PaintImageBuilder&& set_texture_backing(sk_sp<TextureBacking> texture_backing, - PaintImage::ContentId content_id) { - paint_image_.texture_backing_ = std::move(texture_backing); - paint_image_.content_id_ = content_id; - return std::move(*this); - } PaintImage TakePaintImage(); private: + friend class PaintOpReader; + friend class PaintShader; + friend class ImagePaintFilter; + friend PaintImage CreateNonDiscardablePaintImage(const gfx::Size& size); + PaintImageBuilder(); PaintImageBuilder(PaintImage starting_image, bool clear_contents); + // For GPU process callers using a texture backed SkImage. + PaintImageBuilder&& set_texture_image(sk_sp<SkImage> sk_image, + PaintImage::ContentId content_id) { + paint_image_.sk_image_ = std::move(sk_image); + paint_image_.content_id_ = content_id; + return std::move(*this); + } + PaintImage paint_image_; #if DCHECK_IS_ON() bool id_set_ = false; diff --git a/chromium/cc/paint/paint_op_buffer.cc b/chromium/cc/paint/paint_op_buffer.cc index 51955bcf32f..04963e4d4b4 100644 --- a/chromium/cc/paint/paint_op_buffer.cc +++ b/chromium/cc/paint/paint_op_buffer.cc @@ -315,10 +315,7 @@ size_t SimpleSerialize(const PaintOp* op, void* memory, size_t size) { } PlaybackParams::PlaybackParams(ImageProvider* image_provider) - : image_provider(image_provider), - original_ctm(SkMatrix::I()), - custom_callback(CustomDataRasterCallback()), - did_draw_op_callback(DidDrawOpCallback()) {} + : PlaybackParams(image_provider, SkMatrix::I()) {} PlaybackParams::PlaybackParams(ImageProvider* image_provider, const SkMatrix& original_ctm, @@ -1275,7 +1272,8 @@ void DrawImageOp::RasterWithFlags(const DrawImageOp* op, canvas->scale(1.f / op->scale_adjustment.width(), 1.f / op->scale_adjustment.height()); } - canvas->drawImage(op->image.GetSkImage().get(), op->left, op->top, &paint); + canvas->drawImage(op->image.GetRasterSkImage().get(), op->left, op->top, + &paint); return; } @@ -1342,16 +1340,11 @@ void DrawImageRectOp::RasterWithFlags(const DrawImageRectOp* op, return; } - // TODO(enne): Probably PaintCanvas should just use the skia enum directly. - SkCanvas::SrcRectConstraint skconstraint = - static_cast<SkCanvas::SrcRectConstraint>(op->constraint); - if (!params.image_provider) { SkRect adjusted_src = AdjustSrcRectForScale(op->src, op->scale_adjustment); - flags->DrawToSk(canvas, [op, adjusted_src, skconstraint](SkCanvas* c, - const SkPaint& p) { - c->drawImageRect(op->image.GetSkImage().get(), adjusted_src, op->dst, &p, - skconstraint); + flags->DrawToSk(canvas, [op, adjusted_src](SkCanvas* c, const SkPaint& p) { + c->drawImageRect(op->image.GetRasterSkImage().get(), adjusted_src, + op->dst, &p, op->constraint); }); return; } @@ -1381,12 +1374,12 @@ void DrawImageRectOp::RasterWithFlags(const DrawImageRectOp* op, op->src.makeOffset(decoded_image.src_rect_offset().width(), decoded_image.src_rect_offset().height()); adjusted_src = AdjustSrcRectForScale(adjusted_src, scale_adjustment); - flags->DrawToSk(canvas, [op, &decoded_image, adjusted_src, skconstraint]( - SkCanvas* c, const SkPaint& p) { + flags->DrawToSk(canvas, [op, &decoded_image, adjusted_src](SkCanvas* c, + const SkPaint& p) { SkPaint paint_with_filter_quality(p); paint_with_filter_quality.setFilterQuality(decoded_image.filter_quality()); c->drawImageRect(decoded_image.image().get(), adjusted_src, op->dst, - &paint_with_filter_quality, skconstraint); + &paint_with_filter_quality, op->constraint); }); } @@ -1506,7 +1499,19 @@ void SaveLayerAlphaOp::Raster(const SaveLayerAlphaOp* op, const PlaybackParams& params) { // See PaintOp::kUnsetRect bool unset = op->bounds.left() == SK_ScalarInfinity; - canvas->saveLayerAlpha(unset ? nullptr : &op->bounds, op->alpha); + base::Optional<SkPaint> paint; + if (op->alpha != 0xFF) { + paint.emplace(); + paint->setAlpha(op->alpha); + } + SkCanvas::SaveLayerRec rec(unset ? nullptr : &op->bounds, + base::OptionalOrNullptr(paint)); + if (params.save_layer_alpha_should_preserve_lcd_text.has_value() && + *params.save_layer_alpha_should_preserve_lcd_text) { + rec.fSaveLayerFlags = SkCanvas::kPreserveLCDText_SaveLayerFlag | + SkCanvas::kInitWithPrevious_SaveLayerFlag; + } + canvas->saveLayer(rec); } void ScaleOp::Raster(const ScaleOp* op, @@ -2278,6 +2283,18 @@ bool DrawRecordOp::HasNonAAPaint() const { return record->HasNonAAPaint(); } +bool DrawRecordOp::HasDrawTextOps() const { + return record->has_draw_text_ops(); +} + +bool DrawRecordOp::HasSaveLayerAlphaOps() const { + return record->has_save_layer_alpha_ops(); +} + +bool DrawRecordOp::HasEffectsPreventingLCDTextForSaveLayerAlpha() const { + return record->has_effects_preventing_lcd_text_for_save_layer_alpha(); +} + AnnotateOp::AnnotateOp() : PaintOp(kType) {} AnnotateOp::AnnotateOp(PaintCanvas::AnnotationType annotation_type, @@ -2313,7 +2330,7 @@ DrawImageRectOp::DrawImageRectOp(const PaintImage& image, const SkRect& src, const SkRect& dst, const PaintFlags* flags, - PaintCanvas::SrcRectConstraint constraint) + SkCanvas::SrcRectConstraint constraint) : PaintOpWithFlags(kType, flags ? *flags : PaintFlags()), image(image), src(src), @@ -2389,7 +2406,12 @@ PaintOpBuffer::CompositeIterator::CompositeIterator(CompositeIterator&& other) = default; PaintOpBuffer::PaintOpBuffer() - : has_non_aa_paint_(false), has_discardable_images_(false) {} + : has_non_aa_paint_(false), + has_discardable_images_(false), + has_draw_ops_(false), + has_draw_text_ops_(false), + has_save_layer_alpha_ops_(false), + has_effects_preventing_lcd_text_for_save_layer_alpha_(false) {} PaintOpBuffer::PaintOpBuffer(PaintOpBuffer&& other) { *this = std::move(other); @@ -2409,6 +2431,11 @@ PaintOpBuffer& PaintOpBuffer::operator=(PaintOpBuffer&& other) { subrecord_op_count_ = other.subrecord_op_count_; has_non_aa_paint_ = other.has_non_aa_paint_; has_discardable_images_ = other.has_discardable_images_; + has_draw_ops_ = other.has_draw_ops_; + has_draw_text_ops_ = other.has_draw_text_ops_; + has_save_layer_alpha_ops_ = other.has_save_layer_alpha_ops_; + has_effects_preventing_lcd_text_for_save_layer_alpha_ = + other.has_effects_preventing_lcd_text_for_save_layer_alpha_; // Make sure the other pob can destruct safely. other.used_ = 0; @@ -2430,6 +2457,10 @@ void PaintOpBuffer::Reset() { subrecord_bytes_used_ = 0; subrecord_op_count_ = 0; has_discardable_images_ = false; + has_draw_ops_ = false; + has_draw_text_ops_ = false; + has_save_layer_alpha_ops_ = false; + has_effects_preventing_lcd_text_for_save_layer_alpha_ = false; } // When |op| is a nested PaintOpBuffer, this returns the PaintOp inside @@ -2556,10 +2587,22 @@ void PaintOpBuffer::Playback(SkCanvas* canvas, return; if (offsets && offsets->empty()) return; - // Prevent PaintOpBuffers from having side effects back into the canvas. SkAutoCanvasRestore save_restore(canvas, true); + bool save_layer_alpha_should_preserve_lcd_text = + (!params.save_layer_alpha_should_preserve_lcd_text.has_value() || + *params.save_layer_alpha_should_preserve_lcd_text) && + has_draw_text_ops_ && + !has_effects_preventing_lcd_text_for_save_layer_alpha_; + if (save_layer_alpha_should_preserve_lcd_text) { + // Check if the canvas supports LCD text. + SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType); + canvas->getProps(&props); + if (props.pixelGeometry() == kUnknown_SkPixelGeometry) + save_layer_alpha_should_preserve_lcd_text = false; + } + // TODO(enne): a PaintRecord that contains a SetMatrix assumes that the // SetMatrix is local to that PaintRecord itself. Said differently, if you // translate(x, y), then draw a paint record with a SetMatrix(identity), @@ -2568,6 +2611,8 @@ void PaintOpBuffer::Playback(SkCanvas* canvas, PlaybackParams new_params(params.image_provider, canvas->getTotalMatrix(), params.custom_callback, params.did_draw_op_callback); + new_params.save_layer_alpha_should_preserve_lcd_text = + save_layer_alpha_should_preserve_lcd_text; for (PlaybackFoldingIterator iter(this, offsets); iter; ++iter) { const PaintOp* op = *iter; @@ -2591,10 +2636,6 @@ void PaintOpBuffer::Playback(SkCanvas* canvas, if (const auto* raster_flags = scoped_flags.flags()) flags_op->RasterWithFlags(canvas, raster_flags, new_params); } else { - // TODO(enne): skip SaveLayer followed by restore with nothing in - // between, however SaveLayer with image filters on it (or maybe - // other PaintFlags options) are not a noop. Figure out what these - // are so we can skip them correctly. DCHECK_EQ(iter.alpha(), 255); op->Raster(canvas, new_params); } @@ -2719,4 +2760,18 @@ bool PaintOpBuffer::operator==(const PaintOpBuffer& other) const { return true; } +bool PaintOpBuffer::NeedsAdditionalInvalidationForLCDText( + const PaintOpBuffer& old_buffer) const { + // We need this in addition to blink's raster invalidation because change of + // has_effects_preventing_lcd_text_for_save_layer_alpha() can affect + // all SaveLayerAlphaOps of the PaintOpBuffer, not just the area that the + // changed effects affected. + if (!has_draw_text_ops() || !has_save_layer_alpha_ops()) + return false; + if (!old_buffer.has_draw_text_ops() || !old_buffer.has_save_layer_alpha_ops()) + return false; + return has_effects_preventing_lcd_text_for_save_layer_alpha() != + old_buffer.has_effects_preventing_lcd_text_for_save_layer_alpha(); +} + } // namespace cc diff --git a/chromium/cc/paint/paint_op_buffer.h b/chromium/cc/paint/paint_op_buffer.h index ef564407da9..24459b52dd3 100644 --- a/chromium/cc/paint/paint_op_buffer.h +++ b/chromium/cc/paint/paint_op_buffer.h @@ -12,10 +12,11 @@ #include <type_traits> #include "base/callback.h" +#include "base/check_op.h" #include "base/containers/stack_container.h" #include "base/debug/alias.h" -#include "base/logging.h" #include "base/memory/aligned_memory.h" +#include "base/notreached.h" #include "base/optional.h" #include "cc/base/math_util.h" #include "cc/paint/node_id.h" @@ -124,6 +125,7 @@ struct CC_PAINT_EXPORT PlaybackParams { SkMatrix original_ctm; CustomDataRasterCallback custom_callback; DidDrawOpCallback did_draw_op_callback; + base::Optional<bool> save_layer_alpha_should_preserve_lcd_text; }; class CC_PAINT_EXPORT PaintOp { @@ -252,6 +254,11 @@ class CC_PAINT_EXPORT PaintOp { int CountSlowPathsFromFlags() const { return 0; } bool HasNonAAPaint() const { return false; } + bool HasDrawTextOps() const { return false; } + bool HasSaveLayerAlphaOps() const { return false; } + // Returns true if effects are present that would break LCD text or be broken + // by the flags for SaveLayerAlpha to preserving LCD text. + bool HasEffectsPreventingLCDTextForSaveLayerAlpha() const { return false; } bool HasDiscardableImages() const { return false; } bool HasDiscardableImagesFromFlags() const { return false; } @@ -534,7 +541,7 @@ class CC_PAINT_EXPORT DrawImageRectOp final : public PaintOpWithFlags { const SkRect& src, const SkRect& dst, const PaintFlags* flags, - PaintCanvas::SrcRectConstraint constraint); + SkCanvas::SrcRectConstraint constraint); ~DrawImageRectOp(); static void RasterWithFlags(const DrawImageRectOp* op, const PaintFlags* flags, @@ -552,7 +559,7 @@ class CC_PAINT_EXPORT DrawImageRectOp final : public PaintOpWithFlags { PaintImage image; SkRect src; SkRect dst; - PaintCanvas::SrcRectConstraint constraint; + SkCanvas::SrcRectConstraint constraint; private: DrawImageRectOp(); @@ -672,6 +679,9 @@ class CC_PAINT_EXPORT DrawRecordOp final : public PaintOp { bool HasDiscardableImages() const; int CountSlowPaths() const; bool HasNonAAPaint() const; + bool HasDrawTextOps() const; + bool HasSaveLayerAlphaOps() const; + bool HasEffectsPreventingLCDTextForSaveLayerAlpha() const; HAS_SERIALIZATION_FUNCTIONS(); sk_sp<const PaintRecord> record; @@ -759,6 +769,7 @@ class CC_PAINT_EXPORT DrawTextBlobOp final : public PaintOpWithFlags { SkCanvas* canvas, const PlaybackParams& params); bool IsValid() const { return flags.IsValid(); } + bool HasDrawTextOps() const { return true; } static bool AreEqual(const PaintOp* left, const PaintOp* right); HAS_SERIALIZATION_FUNCTIONS(); @@ -835,6 +846,10 @@ class CC_PAINT_EXPORT SaveLayerOp final : public PaintOpWithFlags { bool IsValid() const { return flags.IsValid() && IsValidOrUnsetRect(bounds); } static bool AreEqual(const PaintOp* left, const PaintOp* right); bool HasNonAAPaint() const { return false; } + // We simply assume any effects (or even no effects -- just starting an empty + // transparent layer) would break LCD text or be broken by the flags for + // SaveLayerAlpha to preserve LCD text. + bool HasEffectsPreventingLCDTextForSaveLayerAlpha() const { return true; } HAS_SERIALIZATION_FUNCTIONS(); SkRect bounds; @@ -853,6 +868,7 @@ class CC_PAINT_EXPORT SaveLayerAlphaOp final : public PaintOp { const PlaybackParams& params); bool IsValid() const { return IsValidOrUnsetRect(bounds); } static bool AreEqual(const PaintOp* left, const PaintOp* right); + bool HasSaveLayerAlphaOps() const { return true; } HAS_SERIALIZATION_FUNCTIONS(); SkRect bounds; @@ -985,6 +1001,16 @@ class CC_PAINT_EXPORT PaintOpBuffer : public SkRefCnt { bool HasNonAAPaint() const { return has_non_aa_paint_; } bool HasDiscardableImages() const { return has_discardable_images_; } + bool has_draw_ops() const { return has_draw_ops_; } + bool has_draw_text_ops() const { return has_draw_text_ops_; } + bool has_save_layer_alpha_ops() const { return has_save_layer_alpha_ops_; } + bool has_effects_preventing_lcd_text_for_save_layer_alpha() const { + return has_effects_preventing_lcd_text_for_save_layer_alpha_; + } + + bool NeedsAdditionalInvalidationForLCDText( + const PaintOpBuffer& old_buffer) const; + bool operator==(const PaintOpBuffer& other) const; bool operator!=(const PaintOpBuffer& other) const { return !(*this == other); @@ -1047,6 +1073,12 @@ class CC_PAINT_EXPORT PaintOpBuffer : public SkRefCnt { subrecord_bytes_used_ += op->AdditionalBytesUsed(); subrecord_op_count_ += op->AdditionalOpCount(); + + has_draw_ops_ |= op->IsDrawOp(); + has_draw_text_ops_ |= op->HasDrawTextOps(); + has_save_layer_alpha_ops_ |= op->HasSaveLayerAlphaOps(); + has_effects_preventing_lcd_text_for_save_layer_alpha_ |= + op->HasEffectsPreventingLCDTextForSaveLayerAlpha(); } template <typename T> @@ -1263,15 +1295,19 @@ class CC_PAINT_EXPORT PaintOpBuffer : public SkRefCnt { size_t reserved_ = 0; size_t op_count_ = 0; - // Record paths for veto-to-msaa for gpu raster. - int num_slow_paths_ = 0; // Record additional bytes used by referenced sub-records and display lists. size_t subrecord_bytes_used_ = 0; // Record total op count of referenced sub-record and display lists. size_t subrecord_op_count_ = 0; + // Record paths for veto-to-msaa for gpu raster. + int num_slow_paths_ = 0; bool has_non_aa_paint_ : 1; bool has_discardable_images_ : 1; + bool has_draw_ops_ : 1; + bool has_draw_text_ops_ : 1; + bool has_save_layer_alpha_ops_ : 1; + bool has_effects_preventing_lcd_text_for_save_layer_alpha_ : 1; }; } // namespace cc diff --git a/chromium/cc/paint/paint_op_buffer_fuzzer.cc b/chromium/cc/paint/paint_op_buffer_fuzzer.cc index 48c7b99140d..3f28f0e8d29 100644 --- a/chromium/cc/paint/paint_op_buffer_fuzzer.cc +++ b/chromium/cc/paint/paint_op_buffer_fuzzer.cc @@ -6,6 +6,7 @@ #include <stdint.h> #include "base/command_line.h" +#include "base/logging.h" #include "base/process/memory.h" #include "base/test/test_discardable_memory_allocator.h" #include "cc/paint/paint_cache.h" diff --git a/chromium/cc/paint/paint_op_buffer_serializer.cc b/chromium/cc/paint/paint_op_buffer_serializer.cc index 7601f978325..3f59290175c 100644 --- a/chromium/cc/paint/paint_op_buffer_serializer.cc +++ b/chromium/cc/paint/paint_op_buffer_serializer.cc @@ -4,13 +4,16 @@ #include "cc/paint/paint_op_buffer_serializer.h" +#include <limits> +#include <memory> +#include <utility> +#include <vector> + #include "base/bind.h" #include "base/trace_event/trace_event.h" #include "cc/paint/scoped_raster_flags.h" #include "ui/gfx/skia_util.h" -#include <utility> - namespace cc { namespace { @@ -72,12 +75,16 @@ PaintOpBufferSerializer::PaintOpBufferSerializer( context_supports_distance_field_text_( context_supports_distance_field_text), max_texture_size_(max_texture_size), - text_blob_canvas_(kMaxExtent, - kMaxExtent, - ComputeSurfaceProps(can_use_lcd_text), - strike_server, - std::move(color_space), - context_supports_distance_field_text) { + text_blob_canvas_( + strike_server + ? std::make_unique<SkTextBlobCacheDiffCanvas>( + kMaxExtent, + kMaxExtent, + ComputeSurfaceProps(can_use_lcd_text), + strike_server, + std::move(color_space), + context_supports_distance_field_text) + : std::make_unique<SkNoDrawCanvas>(kMaxExtent, kMaxExtent)) { DCHECK(serialize_cb_); } @@ -86,16 +93,16 @@ PaintOpBufferSerializer::~PaintOpBufferSerializer() = default; void PaintOpBufferSerializer::Serialize(const PaintOpBuffer* buffer, const std::vector<size_t>* offsets, const Preamble& preamble) { - DCHECK(text_blob_canvas_.getTotalMatrix().isIdentity()); + DCHECK(text_blob_canvas_->getTotalMatrix().isIdentity()); static const int kInitialSaveCount = 1; - DCHECK_EQ(kInitialSaveCount, text_blob_canvas_.getSaveCount()); + DCHECK_EQ(kInitialSaveCount, text_blob_canvas_->getSaveCount()); // These SerializeOptions and PlaybackParams use the initial (identity) canvas // matrix, as they are only used for serializing the preamble and the initial // save / final restore. SerializeBuffer will create its own SerializeOptions // and PlaybackParams based on the post-preamble canvas. PaintOp::SerializeOptions options = MakeSerializeOptions(); - PlaybackParams params = MakeParams(&text_blob_canvas_); + PlaybackParams params = MakeParams(text_blob_canvas_.get()); Save(options, params); SerializePreamble(preamble, options, params); @@ -104,7 +111,7 @@ void PaintOpBufferSerializer::Serialize(const PaintOpBuffer* buffer, } void PaintOpBufferSerializer::Serialize(const PaintOpBuffer* buffer) { - DCHECK(text_blob_canvas_.getTotalMatrix().isIdentity()); + DCHECK(text_blob_canvas_->getTotalMatrix().isIdentity()); SerializeBuffer(buffer, nullptr); } @@ -114,10 +121,10 @@ void PaintOpBufferSerializer::Serialize( const gfx::Rect& playback_rect, const gfx::SizeF& post_scale, const SkMatrix& post_matrix_for_analysis) { - DCHECK(text_blob_canvas_.getTotalMatrix().isIdentity()); + DCHECK(text_blob_canvas_->getTotalMatrix().isIdentity()); PaintOp::SerializeOptions options = MakeSerializeOptions(); - PlaybackParams params = MakeParams(&text_blob_canvas_); + PlaybackParams params = MakeParams(text_blob_canvas_.get()); // TODO(khushalsagar): remove this clip rect if it's not needed. if (!playback_rect.IsEmpty()) { @@ -131,7 +138,7 @@ void PaintOpBufferSerializer::Serialize( SerializeOp(&scale_op, options, params); } - text_blob_canvas_.concat(post_matrix_for_analysis); + text_blob_canvas_->concat(post_matrix_for_analysis); SerializeBuffer(buffer, nullptr); } @@ -208,8 +215,10 @@ void PaintOpBufferSerializer::SerializePreamble( << "full: " << preamble.full_raster_rect.ToString() << ", playback: " << preamble.playback_rect.ToString(); + // NOTE: The following code should be kept consistent with + // RasterSource::PlaybackToCanvas(). bool is_partial_raster = preamble.full_raster_rect != preamble.playback_rect; - if (!preamble.requires_clear) { + if (!preamble.requires_clear && preamble.post_translation.IsZero()) { ClearForOpaqueRaster(preamble, options, params); } else if (!is_partial_raster) { // If rastering the entire tile, clear to transparent pre-clip. This is so @@ -259,7 +268,7 @@ void PaintOpBufferSerializer::SerializeBuffer( const std::vector<size_t>* offsets) { DCHECK(buffer); PaintOp::SerializeOptions options = MakeSerializeOptions(); - PlaybackParams params = MakeParams(&text_blob_canvas_); + PlaybackParams params = MakeParams(text_blob_canvas_.get()); for (PaintOpBuffer::PlaybackFoldingIterator iter(buffer, offsets); iter; ++iter) { @@ -267,13 +276,16 @@ void PaintOpBufferSerializer::SerializeBuffer( // Skip ops outside the current clip if they have images. This saves // performing an unnecessary expensive decode. - const bool skip_op = PaintOp::OpHasDiscardableImages(op) && - PaintOp::QuickRejectDraw(op, &text_blob_canvas_); + bool skip_op = PaintOp::OpHasDiscardableImages(op) && + PaintOp::QuickRejectDraw(op, text_blob_canvas_.get()); + // Skip text ops if there is no SkStrikeServer. + skip_op |= + op->GetType() == PaintOpType::DrawTextBlob && !options.strike_server; if (skip_op) continue; if (op->GetType() == PaintOpType::DrawRecord) { - int save_count = text_blob_canvas_.getSaveCount(); + int save_count = text_blob_canvas_->getSaveCount(); Save(options, params); SerializeBuffer(static_cast<const DrawRecordOp*>(op)->record.get(), nullptr); @@ -290,7 +302,7 @@ void PaintOpBufferSerializer::SerializeBuffer( if (!result || !result.paint_record()) continue; - int save_count = text_blob_canvas_.getSaveCount(); + int save_count = text_blob_canvas_->getSaveCount(); Save(options, params); // The following ops are copying the canvas's ops from // DrawImageRectOp::RasterWithFlags. @@ -387,9 +399,9 @@ void PaintOpBufferSerializer::PlaybackOnAnalysisCanvas( if (op->IsPaintOpWithFlags() && options.flags_to_serialize) { static_cast<const PaintOpWithFlags*>(op)->RasterWithFlags( - &text_blob_canvas_, options.flags_to_serialize, params); + text_blob_canvas_.get(), options.flags_to_serialize, params); } else { - op->Raster(&text_blob_canvas_, params); + op->Raster(text_blob_canvas_.get(), params); } } @@ -404,7 +416,7 @@ void PaintOpBufferSerializer::RestoreToCount( const PaintOp::SerializeOptions& options, const PlaybackParams& params) { RestoreOp restore_op; - while (text_blob_canvas_.getSaveCount() > count) { + while (text_blob_canvas_->getSaveCount() > count) { if (!SerializeOp(&restore_op, options, params)) return; } @@ -412,10 +424,10 @@ void PaintOpBufferSerializer::RestoreToCount( PaintOp::SerializeOptions PaintOpBufferSerializer::MakeSerializeOptions() { return PaintOp::SerializeOptions( - image_provider_, transfer_cache_, paint_cache_, &text_blob_canvas_, + image_provider_, transfer_cache_, paint_cache_, text_blob_canvas_.get(), strike_server_, color_space_, can_use_lcd_text_, context_supports_distance_field_text_, max_texture_size_, - text_blob_canvas_.getTotalMatrix()); + text_blob_canvas_->getTotalMatrix()); } SimpleBufferSerializer::SimpleBufferSerializer( diff --git a/chromium/cc/paint/paint_op_buffer_serializer.h b/chromium/cc/paint/paint_op_buffer_serializer.h index 88eb914af4f..3a57a8039cb 100644 --- a/chromium/cc/paint/paint_op_buffer_serializer.h +++ b/chromium/cc/paint/paint_op_buffer_serializer.h @@ -114,7 +114,7 @@ class CC_PAINT_EXPORT PaintOpBufferSerializer { bool context_supports_distance_field_text_; int max_texture_size_; - SkTextBlobCacheDiffCanvas text_blob_canvas_; + std::unique_ptr<SkNoDrawCanvas> text_blob_canvas_; bool valid_ = true; }; diff --git a/chromium/cc/paint/paint_op_buffer_unittest.cc b/chromium/cc/paint/paint_op_buffer_unittest.cc index 7a2861e76a7..fe73fe73b6b 100644 --- a/chromium/cc/paint/paint_op_buffer_unittest.cc +++ b/chromium/cc/paint/paint_op_buffer_unittest.cc @@ -503,16 +503,16 @@ TEST(PaintOpBufferTest, DiscardableImagesTracking_PaintWorkletImageRect) { SkRect src = SkRect::MakeEmpty(); SkRect dst = SkRect::MakeEmpty(); buffer.push<DrawImageRectOp>(image, src, dst, nullptr, - PaintCanvas::kStrict_SrcRectConstraint); + SkCanvas::kStrict_SrcRectConstraint); EXPECT_TRUE(buffer.HasDiscardableImages()); } TEST(PaintOpBufferTest, DiscardableImagesTracking_DrawImageRect) { PaintOpBuffer buffer; PaintImage image = CreateDiscardablePaintImage(gfx::Size(100, 100)); - buffer.push<DrawImageRectOp>( - image, SkRect::MakeWH(100, 100), SkRect::MakeWH(100, 100), nullptr, - PaintCanvas::SrcRectConstraint::kFast_SrcRectConstraint); + buffer.push<DrawImageRectOp>(image, SkRect::MakeWH(100, 100), + SkRect::MakeWH(100, 100), nullptr, + SkCanvas::kFast_SrcRectConstraint); EXPECT_TRUE(buffer.HasDiscardableImages()); } @@ -1081,8 +1081,8 @@ std::vector<uint32_t> test_ids = {0, 1, 56, 0xFFFFFFFF, 0xFFFFFFFE, 0x10001}; std::vector<SkMatrix> test_matrices = { SkMatrix::I(), - SkMatrix::MakeScale(3.91f, 4.31f), - SkMatrix::MakeTrans(-5.2f, 8.7f), + SkMatrix::Scale(3.91f, 4.31f), + SkMatrix::Translate(-5.2f, 8.7f), [] { SkMatrix matrix; SkScalar buffer[] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; @@ -1126,7 +1126,7 @@ std::vector<PaintFlags> test_flags = { flags.setBlendMode(SkBlendMode::kDst); flags.setStrokeCap(PaintFlags::kSquare_Cap); flags.setStrokeJoin(PaintFlags::kBevel_Join); - flags.setStyle(PaintFlags::kStrokeAndFill_Style); + flags.setStyle(PaintFlags::kStroke_Style); flags.setFilterQuality(SkFilterQuality::kMedium_SkFilterQuality); flags.setShader(PaintShader::MakeColor(SkColorSetARGB(1, 2, 3, 4))); return flags; @@ -1467,9 +1467,9 @@ void PushDrawImageRectOps(PaintOpBuffer* buffer) { size_t len = std::min({test_images.size(), test_flags.size(), test_rects.size() - 1}); for (size_t i = 0; i < len; ++i) { - PaintCanvas::SrcRectConstraint constraint = - i % 2 ? PaintCanvas::kStrict_SrcRectConstraint - : PaintCanvas::kFast_SrcRectConstraint; + SkCanvas::SrcRectConstraint constraint = + i % 2 ? SkCanvas::kStrict_SrcRectConstraint + : SkCanvas::kFast_SrcRectConstraint; buffer->push<DrawImageRectOp>(test_images[i], test_rects[i], test_rects[i + 1], &test_flags[i], constraint); @@ -1477,8 +1477,7 @@ void PushDrawImageRectOps(PaintOpBuffer* buffer) { // Test optional flags. buffer->push<DrawImageRectOp>(test_images[0], test_rects[0], test_rects[1], - nullptr, - PaintCanvas::kStrict_SrcRectConstraint); + nullptr, SkCanvas::kStrict_SrcRectConstraint); ValidateOps<DrawImageRectOp>(buffer); } @@ -2602,9 +2601,9 @@ TEST(PaintOpBufferTest, ValidateRects) { buffer.push<ClipRectOp>(bad_rect, SkClipOp::kDifference, true); buffer.push<DrawImageRectOp>(test_images[0], bad_rect, test_rects[1], nullptr, - PaintCanvas::kStrict_SrcRectConstraint); + SkCanvas::kStrict_SrcRectConstraint); buffer.push<DrawImageRectOp>(test_images[0], test_rects[0], bad_rect, nullptr, - PaintCanvas::kStrict_SrcRectConstraint); + SkCanvas::kStrict_SrcRectConstraint); buffer.push<DrawOvalOp>(bad_rect, test_flags[0]); buffer.push<DrawRectOp>(bad_rect, test_flags[0]); buffer.push<SaveLayerOp>(&bad_rect, nullptr); @@ -2925,7 +2924,7 @@ TEST(PaintOpBufferTest, RasterPaintWorkletImageRectBasicCase) { SkRect src = SkRect::MakeXYWH(0, 0, 100, 100); SkRect dst = SkRect::MakeXYWH(0, 0, 100, 100); blink_buffer.push<DrawImageRectOp>(image, src, dst, nullptr, - PaintCanvas::kStrict_SrcRectConstraint); + SkCanvas::kStrict_SrcRectConstraint); testing::StrictMock<MockCanvas> canvas; testing::Sequence s; @@ -2967,7 +2966,7 @@ TEST(PaintOpBufferTest, RasterPaintWorkletImageRectTranslated) { SkRect src = SkRect::MakeXYWH(0, 0, 100, 100); SkRect dst = SkRect::MakeXYWH(5, 7, 100, 100); blink_buffer.push<DrawImageRectOp>(image, src, dst, nullptr, - PaintCanvas::kStrict_SrcRectConstraint); + SkCanvas::kStrict_SrcRectConstraint); testing::StrictMock<MockCanvas> canvas; testing::Sequence s; @@ -2975,7 +2974,7 @@ TEST(PaintOpBufferTest, RasterPaintWorkletImageRectTranslated) { EXPECT_CALL(canvas, willSave()).InSequence(s); EXPECT_CALL(canvas, OnSaveLayer()).InSequence(s); EXPECT_CALL(canvas, OnSaveLayer()).InSequence(s); - EXPECT_CALL(canvas, didConcat(SkMatrix::MakeTrans(5.0f, 7.0f))); + EXPECT_CALL(canvas, didConcat(SkMatrix::Translate(5.0f, 7.0f))); EXPECT_CALL(canvas, willSave()).InSequence(s); EXPECT_CALL(canvas, didScale(1.0f / scale_adjustment[0].width(), 1.0f / scale_adjustment[0].height())); @@ -3012,7 +3011,7 @@ TEST(PaintOpBufferTest, RasterPaintWorkletImageRectScaled) { SkRect src = SkRect::MakeXYWH(0, 0, 100, 100); SkRect dst = SkRect::MakeXYWH(0, 0, 200, 150); blink_buffer.push<DrawImageRectOp>(image, src, dst, nullptr, - PaintCanvas::kStrict_SrcRectConstraint); + SkCanvas::kStrict_SrcRectConstraint); testing::StrictMock<MockCanvas> canvas; testing::Sequence s; @@ -3020,7 +3019,7 @@ TEST(PaintOpBufferTest, RasterPaintWorkletImageRectScaled) { EXPECT_CALL(canvas, willSave()).InSequence(s); EXPECT_CALL(canvas, OnSaveLayer()).InSequence(s); EXPECT_CALL(canvas, OnSaveLayer()).InSequence(s); - EXPECT_CALL(canvas, didConcat(SkMatrix::MakeScale(2.f, 1.5f))); + EXPECT_CALL(canvas, didConcat(SkMatrix::Scale(2.f, 1.5f))); EXPECT_CALL(canvas, willSave()).InSequence(s); EXPECT_CALL(canvas, didScale(1.0f / scale_adjustment[0].width(), 1.0f / scale_adjustment[0].height())); @@ -3060,7 +3059,7 @@ TEST(PaintOpBufferTest, RasterPaintWorkletImageRectClipped) { SkRect src = SkRect::MakeXYWH(0, 0, 20, 20); SkRect dst = SkRect::MakeXYWH(0, 0, 20, 20); blink_buffer.push<DrawImageRectOp>(image, src, dst, nullptr, - PaintCanvas::kStrict_SrcRectConstraint); + SkCanvas::kStrict_SrcRectConstraint); testing::StrictMock<MockCanvas> canvas; testing::Sequence s; @@ -3098,9 +3097,8 @@ TEST(PaintOpBufferTest, ReplacesImagesFromProvider) { flags.setFilterQuality(kLow_SkFilterQuality); PaintImage paint_image = CreateDiscardablePaintImage(gfx::Size(10, 10)); buffer.push<DrawImageOp>(paint_image, 0.0f, 0.0f, &flags); - buffer.push<DrawImageRectOp>( - paint_image, rect, rect, &flags, - PaintCanvas::SrcRectConstraint::kFast_SrcRectConstraint); + buffer.push<DrawImageRectOp>(paint_image, rect, rect, &flags, + SkCanvas::kFast_SrcRectConstraint); flags.setShader(PaintShader::MakeImage(paint_image, SkTileMode::kRepeat, SkTileMode::kRepeat, nullptr)); buffer.push<DrawOvalOp>(SkRect::MakeWH(10, 10), flags); @@ -3142,9 +3140,9 @@ TEST(PaintOpBufferTest, DrawImageRectOpWithLooperNoImageProvider) { PaintFlags paint_flags; paint_flags.setLooper(sk_draw_looper_builder.detach()); - buffer.push<DrawImageRectOp>( - image, SkRect::MakeWH(100, 100), SkRect::MakeWH(100, 100), &paint_flags, - PaintCanvas::SrcRectConstraint::kFast_SrcRectConstraint); + buffer.push<DrawImageRectOp>(image, SkRect::MakeWH(100, 100), + SkRect::MakeWH(100, 100), &paint_flags, + SkCanvas::kFast_SrcRectConstraint); testing::StrictMock<MockCanvas> canvas; EXPECT_CALL(canvas, willSave); @@ -3165,9 +3163,9 @@ TEST(PaintOpBufferTest, DrawImageRectOpWithLooperWithImageProvider) { PaintFlags paint_flags; paint_flags.setLooper(sk_draw_looper_builder.detach()); - buffer.push<DrawImageRectOp>( - image, SkRect::MakeWH(100, 100), SkRect::MakeWH(100, 100), &paint_flags, - PaintCanvas::SrcRectConstraint::kFast_SrcRectConstraint); + buffer.push<DrawImageRectOp>(image, SkRect::MakeWH(100, 100), + SkRect::MakeWH(100, 100), &paint_flags, + SkCanvas::kFast_SrcRectConstraint); testing::StrictMock<MockCanvas> canvas; EXPECT_CALL(canvas, willSave); @@ -3192,9 +3190,8 @@ TEST(PaintOpBufferTest, ReplacesImagesFromProviderOOP) { PaintImage paint_image = CreateDiscardablePaintImage(gfx::Size(10, 10)); buffer.push<ScaleOp>(expected_scale.width(), expected_scale.height()); buffer.push<DrawImageOp>(paint_image, 0.0f, 0.0f, &flags); - buffer.push<DrawImageRectOp>( - paint_image, rect, rect, &flags, - PaintCanvas::SrcRectConstraint::kFast_SrcRectConstraint); + buffer.push<DrawImageRectOp>(paint_image, rect, rect, &flags, + SkCanvas::kFast_SrcRectConstraint); flags.setShader(PaintShader::MakeImage(paint_image, SkTileMode::kRepeat, SkTileMode::kRepeat, nullptr)); buffer.push<DrawOvalOp>(SkRect::MakeWH(10, 10), flags); @@ -3558,9 +3555,9 @@ TEST(PaintOpBufferTest, DrawImageRectSerializeScaledImages) { // translations here are arbitrary SkRect src = SkRect::MakeXYWH(3, 4, 20, 6); SkRect dst = SkRect::MakeXYWH(20, 38, 5, 30); - buffer->push<DrawImageRectOp>( - CreateDiscardablePaintImage(gfx::Size(32, 16)), src, dst, nullptr, - PaintCanvas::SrcRectConstraint::kStrict_SrcRectConstraint); + buffer->push<DrawImageRectOp>(CreateDiscardablePaintImage(gfx::Size(32, 16)), + src, dst, nullptr, + SkCanvas::kStrict_SrcRectConstraint); std::unique_ptr<char, base::AlignedFreeDeleter> memory( static_cast<char*>(base::AlignedAlloc(PaintOpBuffer::kInitialBufferSize, @@ -3829,4 +3826,108 @@ TEST(PaintOpBufferTest, NullImages) { PaintOpType::DrawImage); } +TEST(PaintOpBufferTest, HasDrawOpsAndHasDrawTextOps) { + auto buffer1 = sk_make_sp<PaintOpBuffer>(); + EXPECT_FALSE(buffer1->has_draw_ops()); + EXPECT_FALSE(buffer1->has_draw_text_ops()); + buffer1->push<DrawRectOp>(SkRect::MakeWH(3, 4), PaintFlags()); + PushDrawRectOps(buffer1.get()); + EXPECT_TRUE(buffer1->has_draw_ops()); + EXPECT_FALSE(buffer1->has_draw_text_ops()); + + auto buffer2 = sk_make_sp<PaintOpBuffer>(); + EXPECT_FALSE(buffer2->has_draw_ops()); + EXPECT_FALSE(buffer2->has_draw_text_ops()); + buffer2->push<DrawRecordOp>(std::move(buffer1)); + EXPECT_TRUE(buffer2->has_draw_ops()); + EXPECT_FALSE(buffer2->has_draw_text_ops()); + buffer2->push<DrawTextBlobOp>(SkTextBlob::MakeFromString("abc", SkFont()), 0, + 0, PaintFlags()); + EXPECT_TRUE(buffer2->has_draw_ops()); + EXPECT_TRUE(buffer2->has_draw_text_ops()); + buffer2->push<DrawRectOp>(SkRect::MakeWH(4, 5), PaintFlags()); + EXPECT_TRUE(buffer2->has_draw_ops()); + EXPECT_TRUE(buffer2->has_draw_text_ops()); + + auto buffer3 = sk_make_sp<PaintOpBuffer>(); + EXPECT_FALSE(buffer3->has_draw_text_ops()); + EXPECT_FALSE(buffer3->has_draw_ops()); + buffer3->push<DrawRecordOp>(std::move(buffer2)); + EXPECT_TRUE(buffer3->has_draw_ops()); + EXPECT_TRUE(buffer3->has_draw_text_ops()); +} + +TEST(PaintOpBufferTest, HasEffectsPreventingLCDTextForSaveLayerAlpha) { + auto buffer1 = sk_make_sp<PaintOpBuffer>(); + EXPECT_FALSE(buffer1->has_effects_preventing_lcd_text_for_save_layer_alpha()); + buffer1->push<DrawRectOp>(SkRect::MakeWH(3, 4), PaintFlags()); + EXPECT_FALSE(buffer1->has_effects_preventing_lcd_text_for_save_layer_alpha()); + + auto buffer2 = sk_make_sp<PaintOpBuffer>(); + EXPECT_FALSE(buffer2->has_effects_preventing_lcd_text_for_save_layer_alpha()); + buffer2->push<DrawRecordOp>(std::move(buffer1)); + EXPECT_FALSE(buffer2->has_effects_preventing_lcd_text_for_save_layer_alpha()); + buffer2->push<SaveLayerOp>(nullptr, nullptr); + EXPECT_TRUE(buffer2->has_effects_preventing_lcd_text_for_save_layer_alpha()); + buffer2->push<DrawRectOp>(SkRect::MakeWH(4, 5), PaintFlags()); + EXPECT_TRUE(buffer2->has_effects_preventing_lcd_text_for_save_layer_alpha()); + + auto buffer3 = sk_make_sp<PaintOpBuffer>(); + EXPECT_FALSE(buffer3->has_effects_preventing_lcd_text_for_save_layer_alpha()); + buffer3->push<DrawRecordOp>(std::move(buffer2)); + EXPECT_TRUE(buffer3->has_effects_preventing_lcd_text_for_save_layer_alpha()); +} + +TEST(PaintOpBufferTest, NeedsAdditionalInvalidationForLCDText) { + auto buffer1 = sk_make_sp<PaintOpBuffer>(); + buffer1->push<SaveLayerAlphaOp>(nullptr, 100); + EXPECT_FALSE(buffer1->has_draw_text_ops()); + EXPECT_TRUE(buffer1->has_save_layer_alpha_ops()); + EXPECT_FALSE(buffer1->has_effects_preventing_lcd_text_for_save_layer_alpha()); + + auto buffer2 = sk_make_sp<PaintOpBuffer>(); + buffer2->push<DrawTextBlobOp>(SkTextBlob::MakeFromString("abc", SkFont()), 0, + 0, PaintFlags()); + buffer2->push<SaveLayerOp>(nullptr, nullptr); + EXPECT_TRUE(buffer2->has_draw_ops()); + EXPECT_FALSE(buffer2->has_save_layer_alpha_ops()); + EXPECT_TRUE(buffer2->has_effects_preventing_lcd_text_for_save_layer_alpha()); + + // Neither buffer has effects preventing lcd text for SaveLayerAlpha. + EXPECT_FALSE(buffer1->NeedsAdditionalInvalidationForLCDText(*buffer2)); + EXPECT_FALSE(buffer2->NeedsAdditionalInvalidationForLCDText(*buffer1)); + + { + auto buffer3 = sk_make_sp<PaintOpBuffer>(); + buffer3->push<DrawRecordOp>(buffer2); + EXPECT_TRUE( + buffer3->has_effects_preventing_lcd_text_for_save_layer_alpha()); + // Neither buffer has both DrawText and SaveLayerAlpha. + EXPECT_FALSE(buffer1->NeedsAdditionalInvalidationForLCDText(*buffer3)); + EXPECT_FALSE(buffer3->NeedsAdditionalInvalidationForLCDText(*buffer1)); + EXPECT_FALSE(buffer2->NeedsAdditionalInvalidationForLCDText(*buffer3)); + EXPECT_FALSE(buffer3->NeedsAdditionalInvalidationForLCDText(*buffer2)); + } + { + buffer1->push<DrawTextBlobOp>(SkTextBlob::MakeFromString("abc", SkFont()), + 0, 0, PaintFlags()); + EXPECT_TRUE(buffer1->has_draw_text_ops()); + EXPECT_TRUE(buffer1->has_save_layer_alpha_ops()); + EXPECT_FALSE( + buffer1->has_effects_preventing_lcd_text_for_save_layer_alpha()); + auto buffer3 = sk_make_sp<PaintOpBuffer>(); + buffer3->push<DrawRecordOp>(buffer1); + buffer3->push<DrawRecordOp>(buffer2); + EXPECT_TRUE(buffer3->has_draw_text_ops()); + EXPECT_TRUE(buffer3->has_save_layer_alpha_ops()); + EXPECT_TRUE( + buffer3->has_effects_preventing_lcd_text_for_save_layer_alpha()); + // Both have DrawText and SaveLayerAlpha, and have different + // has_effects_preventing_lcd_text_for_save_layer_alpha(). + EXPECT_TRUE(buffer1->NeedsAdditionalInvalidationForLCDText(*buffer3)); + EXPECT_TRUE(buffer3->NeedsAdditionalInvalidationForLCDText(*buffer1)); + EXPECT_FALSE(buffer3->NeedsAdditionalInvalidationForLCDText(*buffer3)); + } +} + } // namespace cc diff --git a/chromium/cc/paint/paint_op_helper_unittest.cc b/chromium/cc/paint/paint_op_helper_unittest.cc index c2682d039b4..3d5db9a8710 100644 --- a/chromium/cc/paint/paint_op_helper_unittest.cc +++ b/chromium/cc/paint/paint_op_helper_unittest.cc @@ -93,7 +93,7 @@ TEST(PaintOpHelper, DrawImageToString) { TEST(PaintOpHelper, DrawImageRectToString) { DrawImageRectOp op(PaintImage(), SkRect::MakeXYWH(1, 2, 3, 4), SkRect::MakeXYWH(5, 6, 7, 8), nullptr, - PaintCanvas::kStrict_SrcRectConstraint); + SkCanvas::kStrict_SrcRectConstraint); std::string str = PaintOpHelper::ToString(&op); EXPECT_EQ( str, diff --git a/chromium/cc/paint/paint_op_reader.cc b/chromium/cc/paint/paint_op_reader.cc index 5118c0f814a..20a06327cb2 100644 --- a/chromium/cc/paint/paint_op_reader.cc +++ b/chromium/cc/paint/paint_op_reader.cc @@ -326,8 +326,8 @@ void PaintOpReader::Read(PaintImage* image) { *image = PaintImageBuilder::WithDefault() .set_id(PaintImage::GetNextId()) - .set_image(SkImage::MakeRasterCopy(pixmap), - PaintImage::kNonLazyStableId) + .set_texture_image(SkImage::MakeRasterCopy(pixmap), + PaintImage::kNonLazyStableId) .TakePaintImage(); } return; @@ -366,10 +366,11 @@ void PaintOpReader::Read(PaintImage* image) { transfer_cache_entry_id)) { if (needs_mips) entry->EnsureMips(); - *image = PaintImageBuilder::WithDefault() - .set_id(PaintImage::GetNextId()) - .set_image(entry->image(), PaintImage::kNonLazyStableId) - .TakePaintImage(); + *image = + PaintImageBuilder::WithDefault() + .set_id(PaintImage::GetNextId()) + .set_texture_image(entry->image(), PaintImage::kNonLazyStableId) + .TakePaintImage(); } } diff --git a/chromium/cc/paint/paint_op_reader.h b/chromium/cc/paint/paint_op_reader.h index 9de213ec7f0..03d9a241c0f 100644 --- a/chromium/cc/paint/paint_op_reader.h +++ b/chromium/cc/paint/paint_op_reader.h @@ -85,10 +85,10 @@ class CC_PAINT_EXPORT PaintOpReader { Read(&value); *type = static_cast<PaintCanvas::AnnotationType>(value); } - void Read(PaintCanvas::SrcRectConstraint* constraint) { + void Read(SkCanvas::SrcRectConstraint* constraint) { uint8_t value = 0u; Read(&value); - *constraint = static_cast<PaintCanvas::SrcRectConstraint>(value); + *constraint = static_cast<SkCanvas::SrcRectConstraint>(value); } void Read(SkFilterQuality* quality) { uint8_t value = 0u; diff --git a/chromium/cc/paint/paint_op_writer.cc b/chromium/cc/paint/paint_op_writer.cc index a24b5c9ffb5..bb59e6feaff 100644 --- a/chromium/cc/paint/paint_op_writer.cc +++ b/chromium/cc/paint/paint_op_writer.cc @@ -243,7 +243,7 @@ void PaintOpWriter::Write(const DrawImage& draw_image, // Security constrained serialization inlines the image bitmap. if (enable_security_constraints_) { SkBitmap bm; - if (!draw_image.paint_image().GetSkImage()->asLegacyBitmap(&bm)) { + if (!draw_image.paint_image().GetRasterSkImage()->asLegacyBitmap(&bm)) { Write(static_cast<uint8_t>(PaintOp::SerializedImageType::kNoImage)); return; } @@ -741,7 +741,7 @@ void PaintOpWriter::Write(const RecordPaintFilter& filter) { SkMatrix mat = options_.canvas->getTotalMatrix(); SkSize scale; if (!mat.isScaleTranslate() && mat.decomposeScale(&scale)) - mat = SkMatrix::MakeScale(scale.width(), scale.height()); + mat = SkMatrix::Scale(scale.width(), scale.height()); Write(filter.record().get(), gfx::Rect(), gfx::SizeF(1.f, 1.f), mat); } diff --git a/chromium/cc/paint/paint_op_writer.h b/chromium/cc/paint/paint_op_writer.h index 187d697796b..cddf8752d2c 100644 --- a/chromium/cc/paint/paint_op_writer.h +++ b/chromium/cc/paint/paint_op_writer.h @@ -69,7 +69,7 @@ class CC_PAINT_EXPORT PaintOpWriter { void Write(PaintCanvas::AnnotationType type) { Write(static_cast<uint8_t>(type)); } - void Write(PaintCanvas::SrcRectConstraint constraint) { + void Write(SkCanvas::SrcRectConstraint constraint) { Write(static_cast<uint8_t>(constraint)); } void Write(SkFilterQuality filter_quality) { diff --git a/chromium/cc/paint/paint_shader.cc b/chromium/cc/paint/paint_shader.cc index 26761e78266..7b55359a9ab 100644 --- a/chromium/cc/paint/paint_shader.cc +++ b/chromium/cc/paint/paint_shader.cc @@ -395,7 +395,8 @@ sk_sp<PaintShader> PaintShader::CreateDecodedImage( decoded_paint_image = PaintImageBuilder::WithDefault() .set_id(image_.stable_id()) - .set_image(std::move(sk_image), image_.GetContentIdForFrame(0u)) + .set_texture_image(std::move(sk_image), + image_.GetContentIdForFrame(0u)) .TakePaintImage(); } diff --git a/chromium/cc/paint/paint_shader_unittest.cc b/chromium/cc/paint/paint_shader_unittest.cc index f18200efb32..66e8768a083 100644 --- a/chromium/cc/paint/paint_shader_unittest.cc +++ b/chromium/cc/paint/paint_shader_unittest.cc @@ -60,13 +60,13 @@ class MockImageProvider : public ImageProvider { } // namespace TEST(PaintShaderTest, RasterizationRectForRecordShaders) { - SkMatrix local_matrix = SkMatrix::MakeScale(0.5f, 0.5f); + SkMatrix local_matrix = SkMatrix::Scale(0.5f, 0.5f); auto record_shader = PaintShader::MakePaintRecord( sk_make_sp<PaintOpBuffer>(), SkRect::MakeWH(100, 100), SkTileMode::kClamp, SkTileMode::kClamp, &local_matrix); SkRect tile_rect; - SkMatrix ctm = SkMatrix::MakeScale(0.5f, 0.5f); + SkMatrix ctm = SkMatrix::Scale(0.5f, 0.5f); EXPECT_TRUE(record_shader->GetRasterizationTileRect(ctm, &tile_rect)); EXPECT_EQ(tile_rect, SkRect::MakeWH(25, 25)); } @@ -85,7 +85,7 @@ TEST(PaintShaderTest, DecodePaintRecord) { .TakePaintImage(); record->push<DrawImageOp>(paint_image, 0.f, 0.f, nullptr); - SkMatrix local_matrix = SkMatrix::MakeScale(0.5f, 0.5f); + SkMatrix local_matrix = SkMatrix::Scale(0.5f, 0.5f); auto record_shader = PaintShader::MakePaintRecord( record, SkRect::MakeWH(100, 100), SkTileMode::kClamp, SkTileMode::kClamp, &local_matrix); @@ -111,7 +111,7 @@ TEST(PaintShaderTest, DecodePaintRecord) { EXPECT_TRUE(skia_image->isLazyGenerated()); EXPECT_EQ(xy[0], record_shader->tx()); EXPECT_EQ(xy[1], record_shader->ty()); - EXPECT_EQ(decoded_local_matrix, SkMatrix::MakeScale(2.f, 2.f)); + EXPECT_EQ(decoded_local_matrix, SkMatrix::Scale(2.f, 2.f)); // The rasterization of the shader is internal to skia, so use a raster canvas // to verify that the decoded paint does not have the encoded image. diff --git a/chromium/cc/paint/record_paint_canvas.cc b/chromium/cc/paint/record_paint_canvas.cc index 09728c8d7de..e8758c76972 100644 --- a/chromium/cc/paint/record_paint_canvas.cc +++ b/chromium/cc/paint/record_paint_canvas.cc @@ -261,7 +261,7 @@ void RecordPaintCanvas::drawImageRect(const PaintImage& image, const SkRect& src, const SkRect& dst, const PaintFlags* flags, - SrcRectConstraint constraint) { + SkCanvas::SrcRectConstraint constraint) { list_->push<DrawImageRectOp>(image, src, dst, flags, constraint); } diff --git a/chromium/cc/paint/record_paint_canvas.h b/chromium/cc/paint/record_paint_canvas.h index cb1396b3b74..4917088a76e 100644 --- a/chromium/cc/paint/record_paint_canvas.h +++ b/chromium/cc/paint/record_paint_canvas.h @@ -8,7 +8,6 @@ #include <memory> #include "base/compiler_specific.h" -#include "base/logging.h" #include "base/optional.h" #include "build/build_config.h" #include "cc/paint/paint_canvas.h" @@ -85,7 +84,7 @@ class CC_PAINT_EXPORT RecordPaintCanvas : public PaintCanvas { const SkRect& src, const SkRect& dst, const PaintFlags* flags, - SrcRectConstraint constraint) override; + SkCanvas::SrcRectConstraint constraint) override; void drawSkottie(scoped_refptr<SkottieWrapper> skottie, const SkRect& dst, float t) override; diff --git a/chromium/cc/paint/scoped_raster_flags_unittest.cc b/chromium/cc/paint/scoped_raster_flags_unittest.cc index 2a4ac36c6e1..1bef28bc3ac 100644 --- a/chromium/cc/paint/scoped_raster_flags_unittest.cc +++ b/chromium/cc/paint/scoped_raster_flags_unittest.cc @@ -131,13 +131,13 @@ TEST(ScopedRasterFlagsTest, ThinAliasedStroke) { uint8_t expect_alpha; } tests[] = { // No downscaling => no stroke change. - {SkMatrix::MakeScale(1.0f, 1.0f), 255, true, false, 1.0f, 0xFF}, + {SkMatrix::Scale(1.0f, 1.0f), 255, true, false, 1.0f, 0xFF}, // Symmetric downscaling => modulated hairline stroke. - {SkMatrix::MakeScale(0.5f, 0.5f), 255, false, false, 0.0f, 0x80}, + {SkMatrix::Scale(0.5f, 0.5f), 255, false, false, 0.0f, 0x80}, // Symmetric downscaling w/ alpha => modulated hairline stroke. - {SkMatrix::MakeScale(0.5f, 0.5f), 127, false, false, 0.0f, 0x40}, + {SkMatrix::Scale(0.5f, 0.5f), 127, false, false, 0.0f, 0x40}, // Anisotropic scaling => AA stroke. - {SkMatrix::MakeScale(0.5f, 1.5f), 255, false, true, 1.0f, 0xFF}, + {SkMatrix::Scale(0.5f, 1.5f), 255, false, true, 1.0f, 0xFF}, }; for (const auto& test : tests) { diff --git a/chromium/cc/paint/skia_paint_canvas.cc b/chromium/cc/paint/skia_paint_canvas.cc index 91d8acc1d52..3e40ebe88cd 100644 --- a/chromium/cc/paint/skia_paint_canvas.cc +++ b/chromium/cc/paint/skia_paint_canvas.cc @@ -274,7 +274,7 @@ void SkiaPaintCanvas::drawImageRect(const PaintImage& image, const SkRect& src, const SkRect& dst, const PaintFlags* flags, - SrcRectConstraint constraint) { + SkCanvas::SrcRectConstraint constraint) { base::Optional<ScopedRasterFlags> scoped_flags; if (flags) { scoped_flags.emplace(flags, image_provider_, canvas_->getTotalMatrix(), diff --git a/chromium/cc/paint/skia_paint_canvas.h b/chromium/cc/paint/skia_paint_canvas.h index 312433f5d77..77870695946 100644 --- a/chromium/cc/paint/skia_paint_canvas.h +++ b/chromium/cc/paint/skia_paint_canvas.h @@ -8,7 +8,6 @@ #include <memory> #include "base/compiler_specific.h" -#include "base/logging.h" #include "build/build_config.h" #include "cc/paint/paint_canvas.h" #include "cc/paint/paint_flags.h" @@ -109,7 +108,7 @@ class CC_PAINT_EXPORT SkiaPaintCanvas final : public PaintCanvas { const SkRect& src, const SkRect& dst, const PaintFlags* flags, - SrcRectConstraint constraint) override; + SkCanvas::SrcRectConstraint constraint) override; void drawSkottie(scoped_refptr<SkottieWrapper> skottie, const SkRect& dst, float t) override; diff --git a/chromium/cc/raster/gpu_raster_buffer_provider.cc b/chromium/cc/raster/gpu_raster_buffer_provider.cc index aec0c26256c..4dbb25b4d81 100644 --- a/chromium/cc/raster/gpu_raster_buffer_provider.cc +++ b/chromium/cc/raster/gpu_raster_buffer_provider.cc @@ -9,6 +9,7 @@ #include <algorithm> #include <utility> +#include "base/logging.h" #include "base/metrics/histogram_macros.h" #include "base/rand_util.h" #include "base/strings/stringprintf.h" diff --git a/chromium/cc/raster/playback_image_provider.cc b/chromium/cc/raster/playback_image_provider.cc index cbda15cb848..103382658e9 100644 --- a/chromium/cc/raster/playback_image_provider.cc +++ b/chromium/cc/raster/playback_image_provider.cc @@ -6,6 +6,7 @@ #include "base/bind.h" #include "cc/tiles/image_decode_cache.h" +#include "gpu/command_buffer/common/mailbox.h" namespace cc { namespace { @@ -57,9 +58,15 @@ ImageProvider::ScopedResult PlaybackImageProvider::GetRasterContent( DrawImage adjusted_image(draw_image, 1.f, frame_index, target_color_space_); if (!cache_->UseCacheForDrawImage(adjusted_image)) { - return ScopedResult(DecodedDrawImage( - paint_image.GetSkImage(), SkSize::Make(0, 0), SkSize::Make(1.f, 1.f), - draw_image.filter_quality(), true /* is_budgeted */)); + if (settings_->use_oop_raster) { + return ScopedResult(DecodedDrawImage(paint_image.GetMailbox(), + draw_image.filter_quality())); + } else { + return ScopedResult( + DecodedDrawImage(paint_image.GetRasterSkImage(), SkSize::Make(0, 0), + SkSize::Make(1.f, 1.f), draw_image.filter_quality(), + true /* is_budgeted */)); + } } auto decoded_draw_image = cache_->GetDecodedImageForDraw(adjusted_image); diff --git a/chromium/cc/raster/playback_image_provider.h b/chromium/cc/raster/playback_image_provider.h index 70991ca2692..1f0f1084fd6 100644 --- a/chromium/cc/raster/playback_image_provider.h +++ b/chromium/cc/raster/playback_image_provider.h @@ -33,6 +33,10 @@ class CC_EXPORT PlaybackImageProvider : public ImageProvider { // The frame index to use for the given image id. If no index is provided, // the frame index provided in the PaintImage will be used. base::flat_map<PaintImage::Id, size_t> image_to_current_frame_index; + + // Indicates that the consumer of decoded images is paint serialization + // for OOP raster. + bool use_oop_raster = false; }; // If no settings are provided, all images are skipped during rasterization. diff --git a/chromium/cc/raster/raster_source.cc b/chromium/cc/raster/raster_source.cc index 99d6d88018a..73ad6fc23ae 100644 --- a/chromium/cc/raster/raster_source.cc +++ b/chromium/cc/raster/raster_source.cc @@ -5,6 +5,7 @@ #include "cc/raster/raster_source.h" #include <stddef.h> +#include <algorithm> #include "base/metrics/histogram_macros.h" #include "base/trace_event/trace_event.h" @@ -102,8 +103,10 @@ void RasterSource::PlaybackToCanvas( // Treat all subnormal values as zero for performance. ScopedSubnormalFloatDisabler disabler; + // NOTE: The following code should be kept consistent with + // PaintOpBufferSerializer::SerializePreamble(). bool is_partial_raster = canvas_bitmap_rect != canvas_playback_rect; - if (!requires_clear_) { + if (!requires_clear_ && raster_transform.translation().IsZero()) { // Clear opaque raster sources. Opaque rasters sources guarantee that all // pixels inside the opaque region are painted. However, due to scaling // it's possible that the last row and column might include pixels that @@ -134,12 +137,13 @@ void RasterSource::PlaybackToCanvas( raster_canvas->clear(SK_ColorTRANSPARENT); } - PlaybackToCanvas(raster_canvas, settings.image_provider); + PlaybackDisplayListToCanvas(raster_canvas, settings.image_provider); raster_canvas->restore(); } -void RasterSource::PlaybackToCanvas(SkCanvas* raster_canvas, - ImageProvider* image_provider) const { +void RasterSource::PlaybackDisplayListToCanvas( + SkCanvas* raster_canvas, + ImageProvider* image_provider) const { // TODO(enne): Temporary CHECK debugging for http://crbug.com/823835 CHECK(display_list_.get()); int repeat_count = std::max(1, slow_down_raster_scale_factor_for_debug_); diff --git a/chromium/cc/raster/raster_source.h b/chromium/cc/raster/raster_source.h index 7a70742f127..8f7f8679691 100644 --- a/chromium/cc/raster/raster_source.h +++ b/chromium/cc/raster/raster_source.h @@ -56,6 +56,9 @@ class CC_EXPORT RasterSource : public base::RefCountedThreadSafe<RasterSource> { // i.e. contents in the rect will be cropped and translated onto the canvas. // canvas_playback_rect can be used to replay only part of the recording in, // the content space, so only a sub-rect of the tile gets rastered. + // + // Note that this should only be called after the image decode controller has + // been set, which happens during commit. void PlaybackToCanvas(SkCanvas* canvas, const gfx::Size& content_size, const gfx::Rect& canvas_bitmap_rect, @@ -63,17 +66,6 @@ class CC_EXPORT RasterSource : public base::RefCountedThreadSafe<RasterSource> { const gfx::AxisTransform2d& raster_transform, const PlaybackSettings& settings) const; - // Raster this RasterSource into the given canvas. Canvas states such as - // CTM and clip region will be respected. This function will replace pixels - // in the clip region without blending. - // - // Virtual for testing. - // - // Note that this should only be called after the image decode controller has - // been set, which happens during commit. - virtual void PlaybackToCanvas(SkCanvas* canvas, - ImageProvider* image_provider) const; - // Returns whether the given rect at given scale is of solid color in // this raster source, as well as the solid color value. bool PerformSolidColorAnalysis(gfx::Rect content_rect, SkColor* color) const; @@ -139,6 +131,14 @@ class CC_EXPORT RasterSource : public base::RefCountedThreadSafe<RasterSource> { const gfx::Rect& canvas_bitmap_rect, const gfx::Rect& canvas_playback_rect) const; + // Raster the display list of this raster source into the given canvas. + // Canvas states such as CTM and clip region will be respected. + // This function will replace pixels in the clip region without blending. + // + // Virtual for testing. + virtual void PlaybackDisplayListToCanvas(SkCanvas* canvas, + ImageProvider* image_provider) const; + // The serialized size for the largest op in this RasterSource. This is // accessed only on the raster threads with the context lock acquired. size_t max_op_size_hint_ = diff --git a/chromium/cc/raster/scoped_gpu_raster.h b/chromium/cc/raster/scoped_gpu_raster.h index 0ccd91c0259..3d454490bd7 100644 --- a/chromium/cc/raster/scoped_gpu_raster.h +++ b/chromium/cc/raster/scoped_gpu_raster.h @@ -7,7 +7,6 @@ #include <memory> -#include "base/logging.h" #include "cc/cc_export.h" namespace viz { diff --git a/chromium/cc/raster/staging_buffer_pool.cc b/chromium/cc/raster/staging_buffer_pool.cc index fcbe0072eee..556a53615c7 100644 --- a/chromium/cc/raster/staging_buffer_pool.cc +++ b/chromium/cc/raster/staging_buffer_pool.cc @@ -133,9 +133,9 @@ StagingBufferPool::StagingBufferPool( base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider( this, "cc::StagingBufferPool", base::ThreadTaskRunnerHandle::Get()); - memory_pressure_listener_.reset(new base::MemoryPressureListener( - base::BindRepeating(&StagingBufferPool::OnMemoryPressure, - weak_ptr_factory_.GetWeakPtr()))); + memory_pressure_listener_ = std::make_unique<base::MemoryPressureListener>( + FROM_HERE, base::BindRepeating(&StagingBufferPool::OnMemoryPressure, + weak_ptr_factory_.GetWeakPtr())); reduce_memory_usage_callback_ = base::BindRepeating( &StagingBufferPool::ReduceMemoryUsage, weak_ptr_factory_.GetWeakPtr()); diff --git a/chromium/cc/raster/task_graph_runner.h b/chromium/cc/raster/task_graph_runner.h index 50c952bf60c..8a43daab03a 100644 --- a/chromium/cc/raster/task_graph_runner.h +++ b/chromium/cc/raster/task_graph_runner.h @@ -13,7 +13,6 @@ #include <memory> #include <vector> -#include "base/logging.h" #include "base/memory/ref_counted.h" #include "cc/cc_export.h" #include "cc/raster/task.h" diff --git a/chromium/cc/resources/resource_pool.cc b/chromium/cc/resources/resource_pool.cc index 6dbfea69d6b..e854ca403b6 100644 --- a/chromium/cc/resources/resource_pool.cc +++ b/chromium/cc/resources/resource_pool.cc @@ -8,6 +8,7 @@ #include <stdint.h> #include <algorithm> +#include <memory> #include <utility> #include "base/atomic_sequence_num.h" @@ -101,9 +102,9 @@ ResourcePool::ResourcePool( clock_(base::DefaultTickClock::GetInstance()) { base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider( this, "cc::ResourcePool", task_runner_.get()); - memory_pressure_listener_.reset( - new base::MemoryPressureListener(base::BindRepeating( - &ResourcePool::OnMemoryPressure, weak_ptr_factory_.GetWeakPtr()))); + memory_pressure_listener_ = std::make_unique<base::MemoryPressureListener>( + FROM_HERE, base::BindRepeating(&ResourcePool::OnMemoryPressure, + weak_ptr_factory_.GetWeakPtr())); } ResourcePool::~ResourcePool() { diff --git a/chromium/cc/resources/ui_resource_request.h b/chromium/cc/resources/ui_resource_request.h index 6178fde9094..f70233c85d8 100644 --- a/chromium/cc/resources/ui_resource_request.h +++ b/chromium/cc/resources/ui_resource_request.h @@ -7,7 +7,7 @@ #include <memory> -#include "base/logging.h" +#include "base/check.h" #include "cc/cc_export.h" #include "cc/resources/ui_resource_bitmap.h" #include "cc/resources/ui_resource_client.h" diff --git a/chromium/cc/scheduler/commit_earlyout_reason.h b/chromium/cc/scheduler/commit_earlyout_reason.h index 61c1c93d4c9..4dc8b98462b 100644 --- a/chromium/cc/scheduler/commit_earlyout_reason.h +++ b/chromium/cc/scheduler/commit_earlyout_reason.h @@ -5,7 +5,7 @@ #ifndef CC_SCHEDULER_COMMIT_EARLYOUT_REASON_H_ #define CC_SCHEDULER_COMMIT_EARLYOUT_REASON_H_ -#include "base/logging.h" +#include "base/notreached.h" #include "cc/cc_export.h" namespace cc { diff --git a/chromium/cc/scheduler/scheduler.cc b/chromium/cc/scheduler/scheduler.cc index 445e0696639..8292ee70801 100644 --- a/chromium/cc/scheduler/scheduler.cc +++ b/chromium/cc/scheduler/scheduler.cc @@ -516,20 +516,19 @@ void Scheduler::BeginImplFrameWithDeadline(const viz::BeginFrameArgs& args) { ->BeginMainFrameQueueDurationNotCriticalEstimate(); } - // We defer the invalidation if we expect the main thread to respond within - // this frame, and our prediction in the last frame was correct. That - // is, if we predicted the main thread to be fast and it fails to respond - // before the deadline in the previous frame, we don't defer the invalidation - // in the next frame. - const bool main_thread_response_expected_before_deadline = - bmf_sent_to_ready_to_commit_estimate - time_since_main_frame_sent < - bmf_to_activate_threshold; - const bool previous_invalidation_maybe_skipped_for_main_frame = - state_machine_.should_defer_invalidation_for_fast_main_frame() && - state_machine_.main_thread_failed_to_respond_last_deadline(); + bool main_thread_response_expected_before_deadline; + if (time_since_main_frame_sent > bmf_to_activate_threshold) { + // If the response to a main frame is pending past the desired duration + // then proactively assume that the main thread is slow instead of late + // correction through the frame history. + main_thread_response_expected_before_deadline = false; + } else { + main_thread_response_expected_before_deadline = + bmf_sent_to_ready_to_commit_estimate - time_since_main_frame_sent < + bmf_to_activate_threshold; + } state_machine_.set_should_defer_invalidation_for_fast_main_frame( - main_thread_response_expected_before_deadline && - !previous_invalidation_maybe_skipped_for_main_frame); + main_thread_response_expected_before_deadline); base::TimeDelta bmf_to_activate_estimate = bmf_to_activate_estimate_critical; if (!begin_main_frame_args_.on_critical_path) { @@ -594,7 +593,7 @@ void Scheduler::FinishImplFrame() { DCHECK(!inside_scheduled_action_); { base::AutoReset<bool> mark_inside(&inside_scheduled_action_, true); - client_->DidFinishImplFrame(); + client_->DidFinishImplFrame(last_activate_origin_frame_args()); } if (begin_frame_source_) @@ -745,9 +744,6 @@ void Scheduler::DrawIfPossible() { state_machine_.DidDraw(result); compositor_timing_history_->DidDraw( drawing_with_new_active_tree, - client_->CompositedAnimationsCount(), - client_->MainThreadAnimationsCount(), client_->CurrentFrameHadRAF(), - client_->NextFrameHasPendingRAF(), client_->HasCustomPropertyAnimations()); } @@ -763,9 +759,6 @@ void Scheduler::DrawForced() { state_machine_.DidDraw(result); compositor_timing_history_->DidDraw( drawing_with_new_active_tree, - client_->CompositedAnimationsCount(), - client_->MainThreadAnimationsCount(), client_->CurrentFrameHadRAF(), - client_->NextFrameHasPendingRAF(), client_->HasCustomPropertyAnimations()); } diff --git a/chromium/cc/scheduler/scheduler.h b/chromium/cc/scheduler/scheduler.h index 750e44b4614..0265dc0c3ac 100644 --- a/chromium/cc/scheduler/scheduler.h +++ b/chromium/cc/scheduler/scheduler.h @@ -69,7 +69,13 @@ class SchedulerClient { virtual void ScheduledActionInvalidateLayerTreeFrameSink( bool needs_redraw) = 0; virtual void ScheduledActionPerformImplSideInvalidation() = 0; - virtual void DidFinishImplFrame() = 0; + // Called when the scheduler is done processing a frame. Note that the + // BeginFrameArgs instance passed may not necessarily be the same instance + // that was passed to WillBeginImplFrame(). Rather, |last_activated_args| + // represents the latest BeginFrameArgs instance that caused an activation to + // happen. + virtual void DidFinishImplFrame( + const viz::BeginFrameArgs& last_activated_args) = 0; virtual void DidNotProduceFrame(const viz::BeginFrameAck& ack, FrameSkippedReason reason) = 0; virtual void WillNotReceiveBeginFrame() = 0; @@ -79,11 +85,7 @@ class SchedulerClient { virtual void FrameIntervalUpdated(base::TimeDelta interval) = 0; // Functions used for reporting animation targeting UMA, crbug.com/758439. - virtual size_t CompositedAnimationsCount() const = 0; - virtual size_t MainThreadAnimationsCount() const = 0; virtual bool HasCustomPropertyAnimations() const = 0; - virtual bool CurrentFrameHadRAF() const = 0; - virtual bool NextFrameHasPendingRAF() const = 0; protected: virtual ~SchedulerClient() {} @@ -281,7 +283,8 @@ class CC_EXPORT Scheduler : public viz::BeginFrameObserverBase { // What the latest deadline was, and when it was scheduled. base::TimeTicks deadline_; base::TimeTicks deadline_scheduled_at_; - SchedulerStateMachine::BeginImplFrameDeadlineMode deadline_mode_; + SchedulerStateMachine::BeginImplFrameDeadlineMode deadline_mode_ = + SchedulerStateMachine::BeginImplFrameDeadlineMode::NONE; BeginFrameTracker begin_impl_frame_tracker_; viz::BeginFrameAck last_begin_frame_ack_; diff --git a/chromium/cc/scheduler/scheduler_state_machine.cc b/chromium/cc/scheduler/scheduler_state_machine.cc index e69c54ce465..2dcc5e4bba7 100644 --- a/chromium/cc/scheduler/scheduler_state_machine.cc +++ b/chromium/cc/scheduler/scheduler_state_machine.cc @@ -1191,8 +1191,6 @@ void SchedulerStateMachine::OnBeginImplFrameIdle() { // then the main thread is in a high latency mode. main_thread_missed_last_deadline_ = CommitPending() || has_pending_tree_ || active_tree_needs_first_draw_; - main_thread_failed_to_respond_last_deadline_ = - begin_main_frame_state_ == BeginMainFrameState::SENT; // If we're entering a state where we won't get BeginFrames set all the // funnels so that we don't perform any actions that we shouldn't. diff --git a/chromium/cc/scheduler/scheduler_state_machine.h b/chromium/cc/scheduler/scheduler_state_machine.h index f6a8d0a37f1..97b2571f99e 100644 --- a/chromium/cc/scheduler/scheduler_state_machine.h +++ b/chromium/cc/scheduler/scheduler_state_machine.h @@ -344,10 +344,6 @@ class CC_EXPORT SchedulerStateMachine { return should_defer_invalidation_for_fast_main_frame_; } - bool main_thread_failed_to_respond_last_deadline() const { - return main_thread_failed_to_respond_last_deadline_; - } - protected: bool BeginFrameRequiredForAction() const; bool BeginFrameNeededForVideo() const; @@ -471,10 +467,6 @@ class CC_EXPORT SchedulerStateMachine { // tree. During this time we should not activate the pending tree. bool processing_paint_worklets_for_pending_tree_ = false; - // Set to true if the main thread fails to respond with a commit or abort the - // main frame before the draw deadline on the previous impl frame. - bool main_thread_failed_to_respond_last_deadline_ = false; - bool previous_pending_tree_was_impl_side_ = false; bool current_pending_tree_is_impl_side_ = false; diff --git a/chromium/cc/scheduler/scheduler_unittest.cc b/chromium/cc/scheduler/scheduler_unittest.cc index 4ce0028b0f3..e4be2b3e68b 100644 --- a/chromium/cc/scheduler/scheduler_unittest.cc +++ b/chromium/cc/scheduler/scheduler_unittest.cc @@ -121,7 +121,8 @@ class FakeSchedulerClient : public SchedulerClient, scheduler_->SetNeedsRedraw(); return will_begin_impl_frame_might_have_damage_; } - void DidFinishImplFrame() override { + void DidFinishImplFrame( + const viz::BeginFrameArgs& last_activated_args) override { EXPECT_TRUE(inside_begin_impl_frame_); EXPECT_FALSE(inside_action_); base::AutoReset<bool> mark_inside(&inside_action_, true); @@ -262,11 +263,7 @@ class FakeSchedulerClient : public SchedulerClient, PushAction("RemoveObserver(this)"); } - size_t CompositedAnimationsCount() const override { return 0; } - size_t MainThreadAnimationsCount() const override { return 0; } bool HasCustomPropertyAnimations() const override { return false; } - bool CurrentFrameHadRAF() const override { return false; } - bool NextFrameHasPendingRAF() const override { return false; } protected: bool InsideBeginImplFrameCallback(bool state) { @@ -4361,5 +4358,46 @@ TEST_F(SchedulerTest, SendEarlyDidNotProduceFrameIfIdle) { begin_main_frame_args.frame_id.sequence_number); } +TEST_F(SchedulerTest, + HighImplLatencyModePrioritizesMainFramesOverImplInvalidation) { + scheduler_settings_.enable_main_latency_recovery = false; + scheduler_settings_.enable_impl_latency_recovery = false; + SetUpScheduler(EXTERNAL_BFS); + fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration); + + // Place the impl thread in high latency mode. + scheduler_->SetNeedsImplSideInvalidation(true); + client_->Reset(); + EXPECT_SCOPED(AdvanceFrame()); + EXPECT_ACTIONS("WillBeginImplFrame", + "ScheduledActionPerformImplSideInvalidation"); + + // Request a main frame and start the next impl frame. Since we have an impl + // side pending tree, we will activate and draw it. This finishes the impl + // frame before the main thread can respond causing the scheduler to + // incorrectly assume the main thread is slow. + client_->Reset(); + EXPECT_SCOPED(AdvanceFrame()); + EXPECT_ACTIONS("WillBeginImplFrame"); + client_->Reset(); + scheduler_->SetNeedsBeginMainFrame(); + EXPECT_ACTIONS("ScheduledActionSendBeginMainFrame"); + fake_compositor_timing_history_->SetBeginMainFrameSentTime( + task_runner_->NowTicks() + base::TimeDelta::FromMilliseconds(8)); + client_->Reset(); + scheduler_->NotifyReadyToActivate(); + task_runner_->RunTasksWhile(client_->InsideBeginImplFrame(true)); + EXPECT_ACTIONS("ScheduledActionActivateSyncTree", + "ScheduledActionDrawIfPossible"); + + // Start a new frame. We should not assume the main thread is slow. + client_->Reset(); + EXPECT_SCOPED(AdvanceFrame()); + scheduler_->SetNeedsImplSideInvalidation(true); + // No invalidation should be performed since we are waiting for the main + // thread to respond and merge with the commit. + EXPECT_ACTIONS("WillBeginImplFrame"); +} + } // namespace } // namespace cc diff --git a/chromium/cc/tiles/checker_image_tracker.cc b/chromium/cc/tiles/checker_image_tracker.cc index 8495b03dda1..4c932e98d61 100644 --- a/chromium/cc/tiles/checker_image_tracker.cc +++ b/chromium/cc/tiles/checker_image_tracker.cc @@ -4,6 +4,8 @@ #include "cc/tiles/checker_image_tracker.h" +#include <sstream> + #include "base/bind.h" #include "base/metrics/histogram_macros.h" #include "base/stl_util.h" @@ -407,8 +409,7 @@ void CheckerImageTracker::ScheduleNextImageDecode() { draw_image = DrawImage( candidate, SkIRect::MakeWH(candidate.width(), candidate.height()), it->second.filter_quality, - SkMatrix::MakeScale(it->second.scale.width(), - it->second.scale.height()), + SkMatrix::Scale(it->second.scale.width(), it->second.scale.height()), it->second.frame_index, it->second.color_space); outstanding_image_decode_.emplace(candidate); break; diff --git a/chromium/cc/tiles/checker_image_tracker_unittest.cc b/chromium/cc/tiles/checker_image_tracker_unittest.cc index 57984cc8d00..100e27a77d2 100644 --- a/chromium/cc/tiles/checker_image_tracker_unittest.cc +++ b/chromium/cc/tiles/checker_image_tracker_unittest.cc @@ -470,7 +470,7 @@ TEST_F(CheckerImageTrackerTest, ChoosesMaxScaleAndQuality) { gfx::ColorSpace()); DrawImage scaled_image2 = DrawImage(image.paint_image(), image.src_rect(), kHigh_SkFilterQuality, - SkMatrix::MakeScale(1.8f), PaintImage::kDefaultFrameIndex, + SkMatrix::Scale(1.8f, 1.8f), PaintImage::kDefaultFrameIndex, gfx::ColorSpace()); std::vector<DrawImage> draw_images = {scaled_image1, scaled_image2}; diff --git a/chromium/cc/tiles/gpu_image_decode_cache.cc b/chromium/cc/tiles/gpu_image_decode_cache.cc index 4a2d6586f27..1ed44f98cb2 100644 --- a/chromium/cc/tiles/gpu_image_decode_cache.cc +++ b/chromium/cc/tiles/gpu_image_decode_cache.cc @@ -16,6 +16,7 @@ #include "base/debug/alias.h" #include "base/feature_list.h" #include "base/hash/hash.h" +#include "base/logging.h" #include "base/memory/discardable_memory_allocator.h" #include "base/metrics/histogram_macros.h" #include "base/numerics/safe_math.h" @@ -1043,9 +1044,9 @@ GpuImageDecodeCache::GpuImageDecodeCache( base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider( this, "cc::GpuImageDecodeCache", base::ThreadTaskRunnerHandle::Get()); } - memory_pressure_listener_.reset( - new base::MemoryPressureListener(base::BindRepeating( - &GpuImageDecodeCache::OnMemoryPressure, base::Unretained(this)))); + memory_pressure_listener_ = std::make_unique<base::MemoryPressureListener>( + FROM_HERE, base::BindRepeating(&GpuImageDecodeCache::OnMemoryPressure, + base::Unretained(this))); } GpuImageDecodeCache::~GpuImageDecodeCache() { @@ -1164,7 +1165,12 @@ ImageDecodeCache::TaskResult GpuImageDecodeCache::GetTaskForImageAndRefInternal( task = GetImageDecodeTaskAndRef(draw_image, tracing_info, task_type); } - return TaskResult(task, + if (task) { + return TaskResult(task, + image_data->decode.can_do_hardware_accelerated_decode()); + } + + return TaskResult(true /* needs_unref */, false /* is_at_raster_decode */, image_data->decode.can_do_hardware_accelerated_decode()); } @@ -1922,7 +1928,8 @@ void GpuImageDecodeCache::DecodeImageIfNecessary(const DrawImage& draw_image, DLOG(ERROR) << "YUV + Bitmap is unknown and unimplemented!"; NOTREACHED(); } else { - image_data->decode.SetBitmapImage(draw_image.paint_image().GetSkImage()); + image_data->decode.SetBitmapImage( + draw_image.paint_image().GetRasterSkImage()); } return; } @@ -2086,7 +2093,7 @@ void GpuImageDecodeCache::UploadImageIfNecessary(const DrawImage& draw_image, // Get the encoded data in a contiguous form. sk_sp<SkData> encoded_data = - draw_image.paint_image().GetSkImage()->refEncodedData(); + draw_image.paint_image().GetRasterSkImage()->refEncodedData(); DCHECK(encoded_data); const uint32_t transfer_cache_id = ClientImageTransferCacheEntry::GetNextId(); @@ -2526,7 +2533,7 @@ void GpuImageDecodeCache::FlushYUVImages( CheckContextLockAcquiredIfNecessary(); lock_.AssertAcquired(); for (auto& image : *yuv_images) { - image->flush(context_->GrContext()); + image->flushAndSubmit(context_->GrContext()); } yuv_images->clear(); } diff --git a/chromium/cc/tiles/gpu_image_decode_cache.h b/chromium/cc/tiles/gpu_image_decode_cache.h index 4ee3ae8054e..7c345dbbbd5 100644 --- a/chromium/cc/tiles/gpu_image_decode_cache.h +++ b/chromium/cc/tiles/gpu_image_decode_cache.h @@ -11,6 +11,7 @@ #include <vector> #include "base/containers/mru_cache.h" +#include "base/logging.h" #include "base/memory/discardable_memory.h" #include "base/memory/memory_pressure_listener.h" #include "base/optional.h" diff --git a/chromium/cc/tiles/gpu_image_decode_cache_perftest.cc b/chromium/cc/tiles/gpu_image_decode_cache_perftest.cc index 7a9039099bb..416cd8233c8 100644 --- a/chromium/cc/tiles/gpu_image_decode_cache_perftest.cc +++ b/chromium/cc/tiles/gpu_image_decode_cache_perftest.cc @@ -157,7 +157,7 @@ TEST_P(GpuImageDecodeCachePerfTestNoSw, DecodeWithMips) { surface->getCanvas()->drawImageRect(decoded_image.image().get(), SkRect::MakeWH(1024, 2048), SkRect::MakeWH(614, 1229), &paint); - surface->flush(); + surface->flushAndSubmit(); } cache_->DrawWithImageFinished(image, decoded_image); diff --git a/chromium/cc/tiles/gpu_image_decode_cache_unittest.cc b/chromium/cc/tiles/gpu_image_decode_cache_unittest.cc index 3ed5b0b5b94..3c9db3a438e 100644 --- a/chromium/cc/tiles/gpu_image_decode_cache_unittest.cc +++ b/chromium/cc/tiles/gpu_image_decode_cache_unittest.cc @@ -429,7 +429,6 @@ class GpuImageDecodeCacheTest } PaintImage CreateBitmapImageInternal(const gfx::Size& size) { - DCHECK(!do_yuv_decode_); return CreateBitmapImage(size, color_type_); } @@ -2928,6 +2927,20 @@ TEST_P(GpuImageDecodeCacheTest, GetBorderlineLargeDecodedImageForDraw) { cache->UnrefImage(draw_image); } +TEST_P(GpuImageDecodeCacheTest, OutOfRasterDecodeForBitmaps) { + auto cache = CreateCache(); + + PaintImage image = CreateBitmapImageInternal(GetNormalImageSize()); + DrawImage draw_image = CreateDrawImageInternal(image); + ImageDecodeCache::TaskResult result = + cache->GetOutOfRasterDecodeTaskForImageAndRef(draw_image); + EXPECT_TRUE(result.need_unref); + EXPECT_FALSE(result.task); + EXPECT_FALSE(result.is_at_raster_decode); + EXPECT_FALSE(result.can_do_hardware_accelerated_decode); + cache->UnrefImage(draw_image); +} + SkColorType test_color_types[] = {kN32_SkColorType, kARGB_4444_SkColorType, kRGBA_F16_SkColorType}; bool false_array[] = {false}; diff --git a/chromium/cc/tiles/image_controller_unittest.cc b/chromium/cc/tiles/image_controller_unittest.cc index fbe924af6fd..06da97ecd4b 100644 --- a/chromium/cc/tiles/image_controller_unittest.cc +++ b/chromium/cc/tiles/image_controller_unittest.cc @@ -202,7 +202,7 @@ class BlockingTask : public TileTask { EXPECT_FALSE(HasCompleted()); EXPECT_FALSE(thread_checker_.CalledOnValidThread()); base::AutoLock hold(lock_); - if (!can_run_) + while (!can_run_) run_cv_.Wait(); has_run_ = true; } diff --git a/chromium/cc/tiles/image_decode_cache.h b/chromium/cc/tiles/image_decode_cache.h index 6366a6276f6..bd77daad149 100644 --- a/chromium/cc/tiles/image_decode_cache.h +++ b/chromium/cc/tiles/image_decode_cache.h @@ -77,11 +77,24 @@ class CC_EXPORT ImageDecodeCache { ToScopedImageType(ImageType image_type) { using ScopedImageType = devtools_instrumentation::ScopedImageDecodeTask::ImageType; - if (image_type == ImageType::kWEBP) - return ScopedImageType::kWebP; - if (image_type == ImageType::kJPEG) - return ScopedImageType::kJpeg; - return ScopedImageType::kOther; + switch (image_type) { + case ImageType::kAVIF: + return ScopedImageType::kAvif; + case ImageType::kBMP: + return ScopedImageType::kBmp; + case ImageType::kGIF: + return ScopedImageType::kGif; + case ImageType::kICO: + return ScopedImageType::kIco; + case ImageType::kJPEG: + return ScopedImageType::kJpeg; + case ImageType::kPNG: + return ScopedImageType::kPng; + case ImageType::kWEBP: + return ScopedImageType::kWebP; + case ImageType::kInvalid: + return ScopedImageType::kOther; + } } virtual ~ImageDecodeCache() {} diff --git a/chromium/cc/tiles/software_image_decode_cache.cc b/chromium/cc/tiles/software_image_decode_cache.cc index c674a447206..412c1b02c3c 100644 --- a/chromium/cc/tiles/software_image_decode_cache.cc +++ b/chromium/cc/tiles/software_image_decode_cache.cc @@ -82,7 +82,7 @@ class SoftwareImageDecodeTaskImpl : public TileTask { const ImageType image_type = image_metadata ? image_metadata->image_type : ImageType::kInvalid; devtools_instrumentation::ScopedImageDecodeTask image_decode_task( - paint_image_.GetSkImage().get(), + paint_image_.GetRasterSkImage().get(), devtools_instrumentation::ScopedImageDecodeTask::kSoftware, ImageDecodeCache::ToScopedTaskType(tracing_info_.task_type), ImageDecodeCache::ToScopedImageType(image_type)); @@ -134,24 +134,6 @@ SkFilterQuality GetDecodedFilterQuality( : kLow_SkFilterQuality; } -void RecordLockExistingCachedImageHistogram(TilePriority::PriorityBin bin, - bool success) { - switch (bin) { - case TilePriority::NOW: - UMA_HISTOGRAM_BOOLEAN("Renderer4.LockExistingCachedImage.Software.NOW", - success); - break; - case TilePriority::SOON: - UMA_HISTOGRAM_BOOLEAN("Renderer4.LockExistingCachedImage.Software.SOON", - success); - break; - case TilePriority::EVENTUALLY: - UMA_HISTOGRAM_BOOLEAN( - "Renderer4.LockExistingCachedImage.Software.EVENTUALLY", success); - break; - } -} - } // namespace SoftwareImageDecodeCache::SoftwareImageDecodeCache( @@ -357,9 +339,6 @@ SoftwareImageDecodeCache::DecodeImageIfNecessary(const CacheKey& key, return TaskProcessingResult::kLockOnly; bool lock_succeeded = entry->Lock(); - // TODO(vmpstr): Deprecate the prepaint split, since it doesn't matter. - RecordLockExistingCachedImageHistogram(TilePriority::NOW, lock_succeeded); - if (lock_succeeded) return TaskProcessingResult::kLockOnly; } diff --git a/chromium/cc/tiles/software_image_decode_cache_utils.cc b/chromium/cc/tiles/software_image_decode_cache_utils.cc index cfd2e8786cf..5cbb20aba61 100644 --- a/chromium/cc/tiles/software_image_decode_cache_utils.cc +++ b/chromium/cc/tiles/software_image_decode_cache_utils.cc @@ -5,6 +5,7 @@ #include "cc/tiles/software_image_decode_cache_utils.h" #include <algorithm> +#include <sstream> #include <utility> #include "base/atomic_sequence_num.h" diff --git a/chromium/cc/tiles/tile_priority.cc b/chromium/cc/tiles/tile_priority.cc index 606a8c64e0b..1bfe552c057 100644 --- a/chromium/cc/tiles/tile_priority.cc +++ b/chromium/cc/tiles/tile_priority.cc @@ -11,18 +11,6 @@ namespace cc { -std::string WhichTreeToString(WhichTree tree) { - switch (tree) { - case ACTIVE_TREE: - return "ACTIVE_TREE"; - case PENDING_TREE: - return "PENDING_TREE"; - default: - DCHECK(false) << "Unrecognized WhichTree value " << tree; - return "<unknown WhichTree value>"; - } -} - std::string TileResolutionToString(TileResolution resolution) { switch (resolution) { case LOW_RESOLUTION: diff --git a/chromium/cc/tiles/tile_priority.h b/chromium/cc/tiles/tile_priority.h index 34cd3bc0c5d..bffd8d8ee40 100644 --- a/chromium/cc/tiles/tile_priority.h +++ b/chromium/cc/tiles/tile_priority.h @@ -16,10 +16,6 @@ #include "cc/cc_export.h" #include "third_party/perfetto/protos/perfetto/trace/track_event/chrome_compositor_scheduler_state.pbzero.h" -namespace base { -class Value; -} - namespace cc { enum WhichTree { @@ -28,9 +24,7 @@ enum WhichTree { ACTIVE_TREE = 0, PENDING_TREE = 1, LAST_TREE = 1 - // Be sure to update WhichTreeAsValue when adding new fields. }; -std::unique_ptr<base::Value> WhichTreeAsValue(WhichTree tree); enum TileResolution { LOW_RESOLUTION = 0 , diff --git a/chromium/cc/trees/damage_tracker_unittest.cc b/chromium/cc/trees/damage_tracker_unittest.cc index cc784ab1213..db9fcc1fcf6 100644 --- a/chromium/cc/trees/damage_tracker_unittest.cc +++ b/chromium/cc/trees/damage_tracker_unittest.cc @@ -236,6 +236,23 @@ class DamageTrackerTest : public LayerTreeImplTestBase, public testing::Test { return root; } + LayerImpl* CreateAndSetUpTestTreeWithTwoSurfacesDrawingFullyVisible() { + LayerImpl* root = CreateTestTreeWithTwoSurfaces(); + // Make sure render surface takes content outside visible rect into + // consideration. + root->layer_tree_impl() + ->property_trees() + ->effect_tree.Node(child1_->effect_tree_index()) + ->backdrop_filters.Append( + FilterOperation::CreateZoomFilter(2.f /* zoom */, 0 /* inset */)); + + // Setup includes going past the first frame which always damages + // everything, so that we can actually perform specific tests. + EmulateDrawingOneFrame(root); + + return root; + } + LayerImpl* CreateAndSetUpTestTreeWithFourSurfaces() { LayerImpl* root = CreateTestTreeWithFourSurfaces(); @@ -1833,7 +1850,7 @@ TEST_F(DamageTrackerTest, DamageRectTooBigWithFilter) { } TEST_F(DamageTrackerTest, DamageRectTooBigInRenderSurface) { - LayerImpl* root = CreateAndSetUpTestTreeWithTwoSurfaces(); + LayerImpl* root = CreateAndSetUpTestTreeWithTwoSurfacesDrawingFullyVisible(); // Really far left. grand_child1_->SetOffsetToTransformParent( diff --git a/chromium/cc/trees/draw_properties_unittest.cc b/chromium/cc/trees/draw_properties_unittest.cc index 6abe4863df5..b19a402d142 100644 --- a/chromium/cc/trees/draw_properties_unittest.cc +++ b/chromium/cc/trees/draw_properties_unittest.cc @@ -12,6 +12,7 @@ #include "base/memory/ptr_util.h" #include "base/stl_util.h" +#include "base/test/scoped_feature_list.h" #include "cc/animation/animation.h" #include "cc/animation/animation_host.h" #include "cc/animation/animation_id_provider.h" @@ -1415,10 +1416,13 @@ TEST_F(DrawPropertiesTest, DrawableContentRectForLayers) { UpdateActiveTreeDrawProperties(); - EXPECT_EQ(gfx::Rect(5, 5, 10, 10), grand_child1->drawable_content_rect()); - EXPECT_EQ(gfx::Rect(15, 15, 5, 5), grand_child3->drawable_content_rect()); - EXPECT_EQ(gfx::Rect(15, 15, 5, 5), grand_child3->drawable_content_rect()); - EXPECT_TRUE(grand_child4->drawable_content_rect().IsEmpty()); + EXPECT_EQ(gfx::Rect(5, 5, 10, 10), + grand_child1->visible_drawable_content_rect()); + EXPECT_EQ(gfx::Rect(15, 15, 5, 5), + grand_child3->visible_drawable_content_rect()); + EXPECT_EQ(gfx::Rect(15, 15, 5, 5), + grand_child3->visible_drawable_content_rect()); + EXPECT_TRUE(grand_child4->visible_drawable_content_rect().IsEmpty()); } TEST_F(DrawPropertiesTest, ClipRectIsPropagatedCorrectlyToSurfaces) { @@ -1775,7 +1779,7 @@ TEST_F(DrawPropertiesDrawRectsTest, DrawRectsForIdentityTransform) { target_surface_rect, layer_to_surface_transform, layer_content_rect); EXPECT_EQ(expected_visible_layer_rect, drawing_layer->visible_layer_rect()); EXPECT_EQ(expected_drawable_content_rect, - drawing_layer->drawable_content_rect()); + drawing_layer->visible_drawable_content_rect()); // Case 2: Layer is outside the surface rect. layer_content_rect = gfx::Rect(120, 120, 30, 30); @@ -1785,7 +1789,7 @@ TEST_F(DrawPropertiesDrawRectsTest, DrawRectsForIdentityTransform) { target_surface_rect, layer_to_surface_transform, layer_content_rect); EXPECT_EQ(expected_visible_layer_rect, drawing_layer->visible_layer_rect()); EXPECT_EQ(expected_drawable_content_rect, - drawing_layer->drawable_content_rect()); + drawing_layer->visible_drawable_content_rect()); // Case 3: Layer is partially overlapping the surface rect. layer_content_rect = gfx::Rect(80, 80, 30, 30); @@ -1795,7 +1799,7 @@ TEST_F(DrawPropertiesDrawRectsTest, DrawRectsForIdentityTransform) { target_surface_rect, layer_to_surface_transform, layer_content_rect); EXPECT_EQ(expected_visible_layer_rect, drawing_layer->visible_layer_rect()); EXPECT_EQ(expected_drawable_content_rect, - drawing_layer->drawable_content_rect()); + drawing_layer->visible_drawable_content_rect()); } // Test visible layer rect and drawable content rect are calculated correctly @@ -1815,7 +1819,7 @@ TEST_F(DrawPropertiesDrawRectsTest, DrawRectsFor2DRotations) { target_surface_rect, layer_to_surface_transform, layer_content_rect); EXPECT_EQ(expected_visible_layer_rect, drawing_layer->visible_layer_rect()); EXPECT_EQ(expected_drawable_content_rect, - drawing_layer->drawable_content_rect()); + drawing_layer->visible_drawable_content_rect()); // Case 2: Layer is outside the surface rect. layer_to_surface_transform.MakeIdentity(); @@ -1827,7 +1831,7 @@ TEST_F(DrawPropertiesDrawRectsTest, DrawRectsFor2DRotations) { target_surface_rect, layer_to_surface_transform, layer_content_rect); EXPECT_EQ(expected_visible_layer_rect, drawing_layer->visible_layer_rect()); EXPECT_EQ(expected_drawable_content_rect, - drawing_layer->drawable_content_rect()); + drawing_layer->visible_drawable_content_rect()); // Case 3: The layer is rotated about its top-left corner. In surface space, // the layer is oriented diagonally, with the left half outside of the render @@ -1842,7 +1846,7 @@ TEST_F(DrawPropertiesDrawRectsTest, DrawRectsFor2DRotations) { target_surface_rect, layer_to_surface_transform, layer_content_rect); EXPECT_EQ(expected_visible_layer_rect, drawing_layer->visible_layer_rect()); EXPECT_EQ(expected_drawable_content_rect, - drawing_layer->drawable_content_rect()); + drawing_layer->visible_drawable_content_rect()); // Case 4: The layer is rotated about its top-left corner, and translated // upwards. In surface space, the layer is oriented diagonally, with only the @@ -1859,7 +1863,7 @@ TEST_F(DrawPropertiesDrawRectsTest, DrawRectsFor2DRotations) { target_surface_rect, layer_to_surface_transform, layer_content_rect); EXPECT_EQ(expected_visible_layer_rect, drawing_layer->visible_layer_rect()); EXPECT_EQ(expected_drawable_content_rect, - drawing_layer->drawable_content_rect()); + drawing_layer->visible_drawable_content_rect()); } // Test visible layer rect and drawable content rect are calculated correctly @@ -1880,7 +1884,7 @@ TEST_F(DrawPropertiesDrawRectsTest, DrawRectsFor3dOrthographicTransform) { target_surface_rect, layer_to_surface_transform, layer_content_rect); EXPECT_EQ(expected_visible_layer_rect, drawing_layer->visible_layer_rect()); EXPECT_EQ(expected_drawable_content_rect, - drawing_layer->drawable_content_rect()); + drawing_layer->visible_drawable_content_rect()); // Case 2: Orthographic projection of a layer rotated about y-axis by 45 // degrees, but shifted to the side so only the right-half the layer would be @@ -1899,7 +1903,7 @@ TEST_F(DrawPropertiesDrawRectsTest, DrawRectsFor3dOrthographicTransform) { target_surface_rect, layer_to_surface_transform, layer_content_rect); EXPECT_EQ(expected_visible_layer_rect, drawing_layer->visible_layer_rect()); EXPECT_EQ(expected_drawable_content_rect, - drawing_layer->drawable_content_rect()); + drawing_layer->visible_drawable_content_rect()); } // Test visible layer rect and drawable content rect are calculated correctly @@ -1931,7 +1935,7 @@ TEST_F(DrawPropertiesDrawRectsTest, DrawRectsFor3dPerspectiveTransform) { target_surface_rect, layer_to_surface_transform, layer_content_rect); EXPECT_EQ(expected_visible_layer_rect, drawing_layer->visible_layer_rect()); EXPECT_EQ(expected_drawable_content_rect, - drawing_layer->drawable_content_rect()); + drawing_layer->visible_drawable_content_rect()); // Case 2: same projection as before, except that the layer is also translated // to the side, so that only the right half of the layer should be visible. @@ -1951,7 +1955,7 @@ TEST_F(DrawPropertiesDrawRectsTest, DrawRectsFor3dPerspectiveTransform) { target_surface_rect, layer_to_surface_transform, layer_content_rect); EXPECT_EQ(expected_visible_layer_rect, drawing_layer->visible_layer_rect()); EXPECT_EQ(expected_drawable_content_rect, - drawing_layer->drawable_content_rect()); + drawing_layer->visible_drawable_content_rect()); } // There is currently no explicit concept of an orthographic projection plane @@ -1979,7 +1983,7 @@ TEST_F(DrawPropertiesDrawRectsTest, target_surface_rect, layer_to_surface_transform, layer_content_rect); EXPECT_EQ(expected_visible_layer_rect, drawing_layer->visible_layer_rect()); EXPECT_EQ(expected_drawable_content_rect, - drawing_layer->drawable_content_rect()); + drawing_layer->visible_drawable_content_rect()); } // Test visible layer rect and drawable content rect are calculated correctly @@ -2018,7 +2022,7 @@ TEST_F(DrawPropertiesDrawRectsTest, DrawRectsFor3dPerspectiveWhenClippedByW) { target_surface_rect, layer_to_surface_transform, layer_content_rect); EXPECT_EQ(expected_visible_layer_rect, drawing_layer->visible_layer_rect()); EXPECT_EQ(expected_drawable_content_rect, - drawing_layer->drawable_content_rect()); + drawing_layer->visible_drawable_content_rect()); } static bool ProjectionClips(const gfx::Transform& map_transform, @@ -2070,7 +2074,7 @@ TEST_F(DrawPropertiesDrawRectsTest, DrawRectsForPerspectiveUnprojection) { target_surface_rect, layer_to_surface_transform, layer_content_rect); EXPECT_EQ(expected_visible_layer_rect, drawing_layer->visible_layer_rect()); EXPECT_EQ(expected_drawable_content_rect, - drawing_layer->drawable_content_rect()); + drawing_layer->visible_drawable_content_rect()); } TEST_F(DrawPropertiesTest, DrawableAndVisibleContentRectsForSimpleLayers) { @@ -2106,10 +2110,14 @@ TEST_F(DrawPropertiesTest, DrawableAndVisibleContentRectsForSimpleLayers) { EXPECT_EQ(gfx::Rect(0, 0, 25, 25), child2_layer->visible_layer_rect()); EXPECT_TRUE(child3_layer->visible_layer_rect().IsEmpty()); - // layer drawable_content_rects are not clipped. - EXPECT_EQ(gfx::Rect(0, 0, 50, 50), child1_layer->drawable_content_rect()); - EXPECT_EQ(gfx::Rect(75, 75, 50, 50), child2_layer->drawable_content_rect()); - EXPECT_EQ(gfx::Rect(125, 125, 50, 50), child3_layer->drawable_content_rect()); + // layer visible_drawable_content_rects are in target space, but still only + // the visible part. + EXPECT_EQ(gfx::Rect(0, 0, 50, 50), + child1_layer->visible_drawable_content_rect()); + EXPECT_EQ(gfx::Rect(75, 75, 25, 25), + child2_layer->visible_drawable_content_rect()); + EXPECT_EQ(gfx::Rect(125, 125, 0, 0), + child3_layer->visible_drawable_content_rect()); } TEST_F(DrawPropertiesTest, @@ -2153,9 +2161,11 @@ TEST_F(DrawPropertiesTest, EXPECT_TRUE(grand_child3->visible_layer_rect().IsEmpty()); // All grandchild DrawableContentRects should also be clipped by child. - EXPECT_EQ(gfx::Rect(5, 5, 50, 50), grand_child1->drawable_content_rect()); - EXPECT_EQ(gfx::Rect(75, 75, 25, 25), grand_child2->drawable_content_rect()); - EXPECT_TRUE(grand_child3->drawable_content_rect().IsEmpty()); + EXPECT_EQ(gfx::Rect(5, 5, 50, 50), + grand_child1->visible_drawable_content_rect()); + EXPECT_EQ(gfx::Rect(75, 75, 25, 25), + grand_child2->visible_drawable_content_rect()); + EXPECT_TRUE(grand_child3->visible_drawable_content_rect().IsEmpty()); } TEST_F(DrawPropertiesTest, VisibleContentRectWithClippingAndScaling) { @@ -2286,7 +2296,7 @@ TEST_F(DrawPropertiesTest, // An unclipped surface grows its DrawableContentRect to include all drawable // regions of the subtree. - EXPECT_EQ(gfx::RectF(5.f, 5.f, 170.f, 170.f), + EXPECT_EQ(gfx::RectF(5.f, 5.f, 95.f, 95.f), GetRenderSurface(render_surface)->DrawableContentRect()); // All layers that draw content into the unclipped surface are also unclipped. @@ -2295,9 +2305,9 @@ TEST_F(DrawPropertiesTest, EXPECT_EQ(gfx::Rect(0, 0, 25, 25), child2->visible_layer_rect()); EXPECT_EQ(gfx::Rect(0, 0, 0, 0), child3->visible_layer_rect()); - EXPECT_EQ(gfx::Rect(5, 5, 50, 50), child1->drawable_content_rect()); - EXPECT_EQ(gfx::Rect(75, 75, 50, 50), child2->drawable_content_rect()); - EXPECT_EQ(gfx::Rect(125, 125, 50, 50), child3->drawable_content_rect()); + EXPECT_EQ(gfx::Rect(5, 5, 50, 50), child1->visible_drawable_content_rect()); + EXPECT_EQ(gfx::Rect(75, 75, 25, 25), child2->visible_drawable_content_rect()); + EXPECT_EQ(gfx::Rect(125, 125, 0, 0), child3->visible_drawable_content_rect()); } TEST_F(DrawPropertiesTest, VisibleContentRectsForClippedSurfaceWithEmptyClip) { @@ -2361,7 +2371,7 @@ TEST_F(DrawPropertiesTest, UpdateActiveTreeDrawProperties(); EXPECT_TRUE(child->visible_layer_rect().IsEmpty()); - EXPECT_TRUE(child->drawable_content_rect().IsEmpty()); + EXPECT_TRUE(child->visible_drawable_content_rect().IsEmpty()); // Case 2: a matrix with flattened z, uninvertible and not visible according // to the CSS spec. @@ -2373,7 +2383,7 @@ TEST_F(DrawPropertiesTest, UpdateActiveTreeDrawProperties(); EXPECT_TRUE(child->visible_layer_rect().IsEmpty()); - EXPECT_TRUE(child->drawable_content_rect().IsEmpty()); + EXPECT_TRUE(child->visible_drawable_content_rect().IsEmpty()); // Case 3: a matrix with flattened z, also uninvertible and not visible. uninvertible_matrix.MakeIdentity(); @@ -2385,7 +2395,7 @@ TEST_F(DrawPropertiesTest, UpdateActiveTreeDrawProperties(); EXPECT_TRUE(child->visible_layer_rect().IsEmpty()); - EXPECT_TRUE(child->drawable_content_rect().IsEmpty()); + EXPECT_TRUE(child->visible_drawable_content_rect().IsEmpty()); } TEST_F(DrawPropertiesTest, @@ -2592,10 +2602,11 @@ TEST_F(DrawPropertiesTest, EXPECT_EQ(gfx::Rect(0, 0, 25, 25), child2->visible_layer_rect()); EXPECT_TRUE(child3->visible_layer_rect().IsEmpty()); - // But the DrawableContentRects are unclipped. - EXPECT_EQ(gfx::Rect(5, 5, 50, 50), child1->drawable_content_rect()); - EXPECT_EQ(gfx::Rect(75, 75, 50, 50), child2->drawable_content_rect()); - EXPECT_EQ(gfx::Rect(125, 125, 50, 50), child3->drawable_content_rect()); + // The visible_drawable_content_rect would be the visible rect in target + // space. + EXPECT_EQ(gfx::Rect(5, 5, 50, 50), child1->visible_drawable_content_rect()); + EXPECT_EQ(gfx::Rect(75, 75, 25, 25), child2->visible_drawable_content_rect()); + EXPECT_EQ(gfx::Rect(125, 125, 0, 0), child3->visible_drawable_content_rect()); } // Check that clipping does not propagate down surfaces. @@ -2651,8 +2662,9 @@ TEST_F(DrawPropertiesTest, DrawableAndVisibleContentRectsForSurfaceHierarchy) { // render_surface1 lives in the "unclipped universe" of render_surface1, and // is only implicitly clipped by render_surface1's content rect. So, - // render_surface2 grows to enclose all drawable content of its subtree. - EXPECT_EQ(gfx::RectF(5.f, 5.f, 170.f, 170.f), + // render_surface2 grows to enclose all visible drawable content of its + // subtree. + EXPECT_EQ(gfx::RectF(5.f, 5.f, 95.f, 95.f), GetRenderSurface(render_surface2)->DrawableContentRect()); // All layers that draw content into render_surface2 think they are unclipped @@ -2662,9 +2674,9 @@ TEST_F(DrawPropertiesTest, DrawableAndVisibleContentRectsForSurfaceHierarchy) { EXPECT_EQ(gfx::Rect(0, 0, 0, 0), child3->visible_layer_rect()); // DrawableContentRects are also unclipped. - EXPECT_EQ(gfx::Rect(5, 5, 50, 50), child1->drawable_content_rect()); - EXPECT_EQ(gfx::Rect(75, 75, 50, 50), child2->drawable_content_rect()); - EXPECT_EQ(gfx::Rect(125, 125, 50, 50), child3->drawable_content_rect()); + EXPECT_EQ(gfx::Rect(5, 5, 50, 50), child1->visible_drawable_content_rect()); + EXPECT_EQ(gfx::Rect(75, 75, 25, 25), child2->visible_drawable_content_rect()); + EXPECT_EQ(gfx::Rect(125, 125, 0, 0), child3->visible_drawable_content_rect()); } TEST_F(DrawPropertiesTest, @@ -2943,7 +2955,8 @@ TEST_F(DrawPropertiesTest, // All layers that draw content into the unclipped surface are also unclipped. EXPECT_EQ(gfx::Rect(0, 0, 50, 50), child1->visible_layer_rect()); - EXPECT_EQ(expected_surface_drawable_content, child1->drawable_content_rect()); + EXPECT_EQ(expected_surface_drawable_content, + child1->visible_drawable_content_rect()); } // Layers that have non-axis aligned bounds (due to transforms) have an @@ -2967,6 +2980,14 @@ TEST_F(DrawPropertiesTest, CreateEffectNode(render_surface).render_surface_reason = RenderSurfaceReason::kTest; CopyProperties(render_surface, child1); + // Make sure render surface takes content outside visible rect into + // consideration. + root->layer_tree_impl() + ->property_trees() + ->effect_tree.Node(child1->effect_tree_index()) + ->backdrop_filters.Append( + FilterOperation::CreateZoomFilter(1.f /* zoom */, 0 /* inset */)); + auto& child1_transform_node = CreateTransformNode(child1); child1_transform_node.origin = gfx::Point3F(25.f, 25.f, 0.f); child1_transform_node.post_translation = gfx::Vector2dF(25.f, 25.f); @@ -2993,7 +3014,7 @@ TEST_F(DrawPropertiesTest, EXPECT_EQ(gfx::Rect(0, 0, 25, 50), child1->visible_layer_rect()); // The child's DrawableContentRect is unclipped. - EXPECT_EQ(unclipped_surface_content, child1->drawable_content_rect()); + EXPECT_EQ(unclipped_surface_content, child1->visible_drawable_content_rect()); } TEST_F(DrawPropertiesTest, DrawableAndVisibleContentRectsInHighDPI) { @@ -3048,13 +3069,16 @@ TEST_F(DrawPropertiesTest, DrawableAndVisibleContentRectsInHighDPI) { GetRenderSurface(render_surface1)->DrawableContentRect()); // render_surface2 lives in the "unclipped universe" of render_surface1, and - // is only implicitly clipped by render_surface1. - EXPECT_EQ(gfx::RectF(10.f, 10.f, 350.f, 350.f), + // is only implicitly clipped by render_surface1, though it would only contain + // the visible content. + EXPECT_EQ(gfx::RectF(10.f, 10.f, 180.f, 180.f), GetRenderSurface(render_surface2)->DrawableContentRect()); - EXPECT_EQ(gfx::Rect(10, 10, 100, 100), child1->drawable_content_rect()); - EXPECT_EQ(gfx::Rect(150, 150, 100, 100), child2->drawable_content_rect()); - EXPECT_EQ(gfx::Rect(250, 250, 100, 100), child3->drawable_content_rect()); + EXPECT_EQ(gfx::Rect(10, 10, 100, 100), + child1->visible_drawable_content_rect()); + EXPECT_EQ(gfx::Rect(150, 150, 30, 30), + child2->visible_drawable_content_rect()); + EXPECT_EQ(gfx::Rect(250, 250, 0, 0), child3->visible_drawable_content_rect()); // The root layer does not actually draw content of its own. EXPECT_EQ(gfx::Rect(0, 0, 0, 0), root->visible_layer_rect()); @@ -3400,8 +3424,8 @@ TEST_F(DrawPropertiesTest, RenderSurfaceTransformsInHighDPI) { EXPECT_TRANSFORMATION_MATRIX_EQ( child->ScreenSpaceTransform(), duplicate_child_non_owner->ScreenSpaceTransform()); - EXPECT_EQ(child->drawable_content_rect(), - duplicate_child_non_owner->drawable_content_rect()); + EXPECT_EQ(child->visible_drawable_content_rect(), + duplicate_child_non_owner->visible_drawable_content_rect()); EXPECT_EQ(child->bounds(), duplicate_child_non_owner->bounds()); gfx::Transform expected_render_surface_draw_transform; @@ -3552,6 +3576,74 @@ TEST_F(DrawPropertiesTest, OpacityAnimatingOnPendingTree) { active_child->effect_tree_index())); } +class TransformInteropTest : public DrawPropertiesTestBase, + public testing::Test { + public: + TransformInteropTest() : DrawPropertiesTestBase(TransformInteropSettings()) {} + + protected: + LayerTreeSettings TransformInteropSettings() { + LayerListSettings settings; + + settings.enable_transform_interop = true; + return settings; + } +}; + +TEST_F(TransformInteropTest, BackfaceInvisibleTransform) { + LayerImpl* root = root_layer(); + root->SetDrawsContent(true); + LayerImpl* back_facing = AddLayer<LayerImpl>(); + LayerImpl* back_facing_double_sided = AddLayer<LayerImpl>(); + LayerImpl* front_facing = AddLayer<LayerImpl>(); + back_facing->SetDrawsContent(true); + back_facing_double_sided->SetDrawsContent(true); + front_facing->SetDrawsContent(true); + + root->SetBounds(gfx::Size(50, 50)); + back_facing->SetBounds(gfx::Size(50, 50)); + back_facing_double_sided->SetBounds(gfx::Size(50, 50)); + front_facing->SetBounds(gfx::Size(50, 50)); + CopyProperties(root, back_facing); + CopyProperties(root, front_facing); + + back_facing->SetShouldCheckBackfaceVisibility(true); + back_facing_double_sided->SetShouldCheckBackfaceVisibility(false); + front_facing->SetShouldCheckBackfaceVisibility(true); + + auto& back_facing_transform_node = CreateTransformNode(back_facing); + back_facing_transform_node.flattens_inherited_transform = false; + back_facing_transform_node.sorting_context_id = 1; + gfx::Transform rotate_about_y; + rotate_about_y.RotateAboutYAxis(180.0); + back_facing_transform_node.local = rotate_about_y; + + CopyProperties(back_facing, back_facing_double_sided); + + UpdateActiveTreeDrawProperties(); + + EXPECT_TRUE(draw_property_utils::IsLayerBackFaceVisible( + back_facing, back_facing->transform_tree_index(), + host_impl()->active_tree()->property_trees())); + EXPECT_TRUE(draw_property_utils::IsLayerBackFaceVisible( + back_facing, back_facing_double_sided->transform_tree_index(), + host_impl()->active_tree()->property_trees())); + EXPECT_FALSE(draw_property_utils::IsLayerBackFaceVisible( + front_facing, front_facing->transform_tree_index(), + host_impl()->active_tree()->property_trees())); + + EXPECT_TRUE( + draw_property_utils::LayerShouldBeSkippedForDrawPropertiesComputation( + back_facing, host_impl()->active_tree()->property_trees())); + EXPECT_FALSE( + draw_property_utils::LayerShouldBeSkippedForDrawPropertiesComputation( + back_facing_double_sided, + host_impl()->active_tree()->property_trees())); + EXPECT_FALSE( + draw_property_utils::LayerShouldBeSkippedForDrawPropertiesComputation( + front_facing, host_impl()->active_tree()->property_trees())); +} + using LCDTextTestParam = std::tuple<bool, bool>; class LCDTextTest : public DrawPropertiesTestBase, public testing::TestWithParam<LCDTextTestParam> { @@ -3699,6 +3791,16 @@ TEST_P(LCDTextTest, CanUseLCDText) { CheckCanUseLCDText(LCDTextDisallowedReason::kNone, grand_child_); } +TEST_P(LCDTextTest, CanUseLCDTextWithContentsOpaqueForText) { + child_->SetContentsOpaque(false); + child_->SetBackgroundColor(SK_ColorGREEN); + child_->SetContentsOpaqueForText(true); + CheckCanUseLCDText(LCDTextDisallowedReason::kNone, child_); + + child_->SetContentsOpaqueForText(false); + CheckCanUseLCDText(LCDTextDisallowedReason::kContentsNotOpaque, child_); +} + TEST_P(LCDTextTest, CanUseLCDTextWithAnimation) { // Sanity check: Make sure can_use_lcd_text_ is set on each node. UpdateActiveTreeDrawProperties(); @@ -4232,19 +4334,19 @@ TEST_F(DrawPropertiesTest, ClipParentWithInterveningRenderSurface) { EXPECT_EQ(gfx::Rect(0, 0, 5, 5), render_surface2->clip_rect()); EXPECT_TRUE(render_surface2->is_clipped()); - // The content rects of render_surface2 should have expanded to contain the - // clip child. - EXPECT_EQ(gfx::Rect(0, 0, 40, 40), - GetRenderSurface(render_surface1)->content_rect()); - EXPECT_EQ(gfx::Rect(-10, -10, 60, 60), - GetRenderSurface(render_surface2)->content_rect()); - // The clip child should have inherited the clip parent's clip (projected to // the right space, of course), but as render_surface1 already applies that // clip, clip_child need not apply it again. EXPECT_EQ(gfx::Rect(), clip_child->clip_rect()); EXPECT_EQ(gfx::Rect(9, 9, 40, 40), clip_child->visible_layer_rect()); EXPECT_FALSE(clip_child->is_clipped()); + + // The content rects of render_surface2 should have expanded to contain the + // clip child, but only the visible part of the clip child. + EXPECT_EQ(gfx::Rect(0, 0, 40, 40), + GetRenderSurface(render_surface1)->content_rect()); + EXPECT_EQ(clip_child->visible_drawable_content_rect(), + GetRenderSurface(render_surface2)->content_rect()); } TEST_F(DrawPropertiesTest, ClipParentScrolledInterveningLayer) { @@ -4329,19 +4431,19 @@ TEST_F(DrawPropertiesTest, ClipParentScrolledInterveningLayer) { EXPECT_EQ(gfx::Rect(0, 0, 5, 5), render_surface2->clip_rect()); EXPECT_TRUE(render_surface2->is_clipped()); - // The content rects of render_surface2 should have expanded to contain the - // clip child. - EXPECT_EQ(gfx::Rect(0, 0, 40, 40), - GetRenderSurface(render_surface1)->content_rect()); - EXPECT_EQ(gfx::Rect(-10, -10, 60, 60), - GetRenderSurface(render_surface2)->content_rect()); - // The clip child should have inherited the clip parent's clip (projected to // the right space, of course), but as render_surface1 already applies that // clip, clip_child need not apply it again. EXPECT_EQ(gfx::Rect(), clip_child->clip_rect()); EXPECT_EQ(gfx::Rect(12, 12, 40, 40), clip_child->visible_layer_rect()); EXPECT_FALSE(clip_child->is_clipped()); + + // The content rects of render_surface2 should have expanded to contain the + // clip child, but only the visible part of the clip child. + EXPECT_EQ(gfx::Rect(0, 0, 40, 40), + GetRenderSurface(render_surface1)->content_rect()); + EXPECT_EQ(clip_child->visible_drawable_content_rect(), + GetRenderSurface(render_surface2)->content_rect()); } TEST_F(DrawPropertiesTest, DescendantsOfClipChildren) { @@ -6904,7 +7006,8 @@ TEST_F(DrawPropertiesTest, RenderSurfaceWithUnclippedDescendantsClipsSubtree) { EXPECT_TRUE(test_layer->is_clipped()); EXPECT_FALSE(test_layer->render_target()->is_clipped()); EXPECT_EQ(gfx::Rect(-2, -2, 30, 30), test_layer->clip_rect()); - EXPECT_EQ(gfx::Rect(28, 28), test_layer->drawable_content_rect()); + EXPECT_EQ(gfx::Rect(26, 26), test_layer->visible_layer_rect()); + EXPECT_EQ(gfx::Rect(26, 26), test_layer->visible_drawable_content_rect()); } TEST_F(DrawPropertiesTest, @@ -7059,7 +7162,7 @@ TEST_F(DrawPropertiesTest, RenderSurfaceContentRectWithMultipleSurfaces) { GetRenderSurface(unclipped_surface)->content_rect()); EXPECT_EQ(gfx::Rect(50, 50), GetRenderSurface(unclipped_desc_surface)->content_rect()); - EXPECT_EQ(gfx::Rect(60, 60), + EXPECT_EQ(gfx::Rect(50, 50), GetRenderSurface(unclipped_desc_surface2)->content_rect()); EXPECT_EQ(gfx::Rect(50, 50), GetRenderSurface(clipped_surface)->content_rect()); @@ -7724,7 +7827,8 @@ TEST_F(DrawPropertiesTestWithLayerTree, CopyRequestScalingTest) { EXPECT_EQ(gfx::Rect(10, 10), ImplOf(test_layer)->visible_layer_rect()); EXPECT_EQ(transform, ImplOf(test_layer)->DrawTransform()); EXPECT_EQ(gfx::Rect(50, 50), ImplOf(test_layer)->clip_rect()); - EXPECT_EQ(gfx::Rect(50, 50), ImplOf(test_layer)->drawable_content_rect()); + EXPECT_EQ(gfx::Rect(50, 50), + ImplOf(test_layer)->visible_drawable_content_rect()); // Clear the copy request and call UpdateSurfaceContentsScale. GetPropertyTrees(root.get())->effect_tree.ClearCopyRequests(); diff --git a/chromium/cc/trees/draw_property_utils.cc b/chromium/cc/trees/draw_property_utils.cc index f9fdebf8218..7c65e513700 100644 --- a/chromium/cc/trees/draw_property_utils.cc +++ b/chromium/cc/trees/draw_property_utils.cc @@ -11,6 +11,7 @@ #include "base/containers/adapters.h" #include "base/containers/stack.h" +#include "base/logging.h" #include "cc/base/math_util.h" #include "cc/layers/draw_properties.h" #include "cc/layers/layer.h" @@ -381,6 +382,12 @@ bool IsTargetSpaceTransformBackFaceVisible( LayerImpl* layer, int transform_tree_index, const PropertyTrees* property_trees) { + const TransformTree& transform_tree = property_trees->transform_tree; + const TransformNode& transform_node = + *transform_tree.Node(transform_tree_index); + if (transform_node.delegates_to_parent_for_backface) + transform_tree_index = transform_node.parent_id; + gfx::Transform to_target; property_trees->GetToTarget(transform_tree_index, layer->render_target_effect_tree_index(), @@ -388,12 +395,42 @@ bool IsTargetSpaceTransformBackFaceVisible( return to_target.IsBackFaceVisible(); } -template <typename LayerType> -bool IsLayerBackFaceVisible(LayerType* layer, - int transform_tree_index, - const PropertyTrees* property_trees) { - return IsTargetSpaceTransformBackFaceVisible(layer, transform_tree_index, - property_trees); +bool IsTransformToRootOf3DRenderingContextBackFaceVisible( + Layer* layer, + int transform_tree_index, + const PropertyTrees* property_trees) { + // We do not skip back face invisible layers on main thread as target space + // transform will not be available here. + return false; +} + +bool IsTransformToRootOf3DRenderingContextBackFaceVisible( + LayerImpl* layer, + int transform_tree_index, + const PropertyTrees* property_trees) { + const TransformTree& transform_tree = property_trees->transform_tree; + + const TransformNode& transform_node = + *transform_tree.Node(transform_tree_index); + if (transform_node.delegates_to_parent_for_backface) + transform_tree_index = transform_node.parent_id; + + int root_id = transform_tree_index; + int sorting_context_id = transform_node.sorting_context_id; + + while (root_id > 0 && transform_tree.Node(root_id - 1)->sorting_context_id == + sorting_context_id) + root_id--; + + // TODO(chrishtr): cache this on the transform trees if needed, similar to + // |to_target| and |to_screen|. + gfx::Transform to_3d_root; + if (transform_tree_index != root_id) + property_trees->transform_tree.CombineTransformsBetween( + transform_tree_index, root_id, &to_3d_root); + to_3d_root.PreconcatTransform( + property_trees->transform_tree.Node(root_id)->to_parent); + return to_3d_root.IsBackFaceVisible(); } inline bool TransformToScreenIsKnown(Layer* layer, @@ -445,7 +482,8 @@ bool LayerNeedsUpdate(LayerType* layer, // backface is not visible. if (TransformToScreenIsKnown(layer, backface_transform_id, tree) && !HasSingularTransform(backface_transform_id, tree) && - IsLayerBackFaceVisible(layer, backface_transform_id, property_trees)) { + draw_property_utils::IsLayerBackFaceVisible( + layer, backface_transform_id, property_trees)) { UMA_HISTOGRAM_BOOLEAN( "Compositing.Renderer.LayerUpdateSkippedDueToBackface", true); return false; @@ -474,35 +512,6 @@ inline bool LayerShouldBeSkippedForDrawPropertiesComputation( !effect_node->is_drawn; } -inline bool LayerShouldBeSkippedForDrawPropertiesComputation( - LayerImpl* layer, - const TransformTree& transform_tree, - const EffectTree& effect_tree) { - const EffectNode* effect_node = effect_tree.Node(layer->effect_tree_index()); - - if (effect_node->HasRenderSurface() && effect_node->subtree_has_copy_request) - return false; - - // Skip if the node's subtree is hidden and no need to cache. - if (effect_node->subtree_hidden && !effect_node->cache_render_surface) - return true; - - // If the layer transform is not invertible, it should be skipped. In case the - // transform is animating and singular, we should not skip it. - const TransformNode* transform_node = - transform_tree.Node(layer->transform_tree_index()); - - if (!transform_node->node_and_ancestors_are_animated_or_invertible || - !effect_node->is_drawn) - return true; - - UMA_HISTOGRAM_BOOLEAN( - "Compositing.Renderer.LayerSkippedForDrawPropertiesDueToBackface", - effect_node->hidden_by_backface_visibility); - - return effect_node->hidden_by_backface_visibility; -} - gfx::Rect LayerDrawableContentRect( const LayerImpl* layer, const gfx::Rect& layer_bounds_in_target_space, @@ -922,8 +931,8 @@ void ComputeInitialRenderSurfaceList(LayerTreeImpl* layer_tree_impl, bool is_root = layer_tree_impl->IsRootLayer(layer); bool skip_draw_properties_computation = - LayerShouldBeSkippedForDrawPropertiesComputation( - layer, property_trees->transform_tree, property_trees->effect_tree); + draw_property_utils::LayerShouldBeSkippedForDrawPropertiesComputation( + layer, property_trees); bool skip_for_invertibility = SkipForInvertibility(layer, property_trees); @@ -1138,11 +1147,19 @@ void ComputeDrawPropertiesOfVisibleLayers(const LayerImplList* layer_list, // Compute drawable content rects for (LayerImpl* layer : *layer_list) { - gfx::Rect bounds_in_target_space = MathUtil::MapEnclosingClippedRect( - layer->draw_properties().target_space_transform, - gfx::Rect(layer->bounds())); - layer->draw_properties().drawable_content_rect = LayerDrawableContentRect( - layer, bounds_in_target_space, layer->draw_properties().clip_rect); + bool only_draws_visible_content = + property_trees->effect_tree.Node(layer->effect_tree_index()) + ->only_draws_visible_content; + gfx::Rect drawable_bounds = gfx::Rect(layer->visible_layer_rect()); + if (!only_draws_visible_content) { + drawable_bounds = gfx::Rect(layer->bounds()); + } + gfx::Rect visible_bounds_in_target_space = + MathUtil::MapEnclosingClippedRect( + layer->draw_properties().target_space_transform, drawable_bounds); + layer->draw_properties().visible_drawable_content_rect = + LayerDrawableContentRect(layer, visible_bounds_in_target_space, + layer->draw_properties().clip_rect); } } @@ -1164,6 +1181,61 @@ bool NodeMayContainBackdropBlurFilter(const EffectNode& node) { } // namespace +bool CC_EXPORT LayerShouldBeSkippedForDrawPropertiesComputation( + LayerImpl* layer, + const PropertyTrees* property_trees) { + const TransformTree& transform_tree = property_trees->transform_tree; + const EffectTree& effect_tree = property_trees->effect_tree; + const EffectNode* effect_node = effect_tree.Node(layer->effect_tree_index()); + + if (effect_node->HasRenderSurface() && effect_node->subtree_has_copy_request) + return false; + + // Skip if the node's subtree is hidden and no need to cache. + if (effect_node->subtree_hidden && !effect_node->cache_render_surface) + return true; + + // If the layer transform is not invertible, it should be skipped. In case the + // transform is animating and singular, we should not skip it. + const TransformNode* transform_node = + transform_tree.Node(layer->transform_tree_index()); + + if (!transform_node->node_and_ancestors_are_animated_or_invertible || + !effect_node->is_drawn) + return true; + if (layer->layer_tree_impl()->settings().enable_transform_interop) { + return layer->should_check_backface_visibility() && + IsLayerBackFaceVisible(layer, layer->transform_tree_index(), + property_trees); + } else { + return effect_node->hidden_by_backface_visibility; + } +} + +bool CC_EXPORT IsLayerBackFaceVisible(LayerImpl* layer, + int transform_tree_index, + const PropertyTrees* property_trees) { + if (layer->layer_tree_impl()->settings().enable_transform_interop) { + return IsTransformToRootOf3DRenderingContextBackFaceVisible( + layer, transform_tree_index, property_trees); + } else { + return IsTargetSpaceTransformBackFaceVisible(layer, transform_tree_index, + property_trees); + } +} + +bool CC_EXPORT IsLayerBackFaceVisible(Layer* layer, + int transform_tree_index, + const PropertyTrees* property_trees) { + if (layer->layer_tree_host()->GetSettings().enable_transform_interop) { + return IsTransformToRootOf3DRenderingContextBackFaceVisible( + layer, transform_tree_index, property_trees); + } else { + return IsTargetSpaceTransformBackFaceVisible(layer, transform_tree_index, + property_trees); + } +} + void ConcatInverseSurfaceContentsScale(const EffectNode* effect_node, gfx::Transform* transform) { DCHECK(effect_node->HasRenderSurface()); @@ -1195,7 +1267,6 @@ void FindLayersThatNeedUpdates(LayerTreeHost* layer_tree_host, void FindLayersThatNeedUpdates(LayerTreeImpl* layer_tree_impl, std::vector<LayerImpl*>* visible_layer_list) { const PropertyTrees* property_trees = layer_tree_impl->property_trees(); - const TransformTree& transform_tree = property_trees->transform_tree; const EffectTree& effect_tree = property_trees->effect_tree; for (auto* layer_impl : *layer_tree_impl) { @@ -1204,8 +1275,8 @@ void FindLayersThatNeedUpdates(LayerTreeImpl* layer_tree_impl, layer_impl->EnsureValidPropertyTreeIndices(); if (!IsRootLayer(layer_impl) && - LayerShouldBeSkippedForDrawPropertiesComputation( - layer_impl, transform_tree, effect_tree)) + LayerShouldBeSkippedForDrawPropertiesComputation(layer_impl, + property_trees)) continue; bool layer_is_drawn = diff --git a/chromium/cc/trees/draw_property_utils.h b/chromium/cc/trees/draw_property_utils.h index c42684eefce..b80b38e3ab9 100644 --- a/chromium/cc/trees/draw_property_utils.h +++ b/chromium/cc/trees/draw_property_utils.h @@ -68,6 +68,18 @@ void CC_EXPORT CalculateDrawProperties( RenderSurfaceList* output_render_surface_list, LayerImplList* output_update_layer_list_for_testing = nullptr); +bool CC_EXPORT LayerShouldBeSkippedForDrawPropertiesComputation( + LayerImpl* layer, + const PropertyTrees* property_trees); + +bool CC_EXPORT IsLayerBackFaceVisible(LayerImpl* layer, + int transform_tree_index, + const PropertyTrees* property_trees); + +bool CC_EXPORT IsLayerBackFaceVisible(Layer* layer, + int transform_tree_index, + const PropertyTrees* property_trees); + #if DCHECK_IS_ON() // Checks and logs if double background blur exists in any layers. Returns // true if no double background blur is detected, false otherwise. diff --git a/chromium/cc/trees/effect_node.cc b/chromium/cc/trees/effect_node.cc index a412c237f1c..9bae1e90b74 100644 --- a/chromium/cc/trees/effect_node.cc +++ b/chromium/cc/trees/effect_node.cc @@ -23,6 +23,7 @@ EffectNode::EffectNode() double_sided(true), trilinear_filtering(false), is_drawn(true), + only_draws_visible_content(true), subtree_hidden(false), has_potential_filter_animation(false), has_potential_backdrop_filter_animation(false), @@ -56,7 +57,6 @@ bool EffectNode::operator==(const EffectNode& other) const { backdrop_filters == other.backdrop_filters && backdrop_filter_bounds == other.backdrop_filter_bounds && backdrop_mask_element_id == other.backdrop_mask_element_id && - filters_origin == other.filters_origin && rounded_corner_bounds == other.rounded_corner_bounds && is_fast_rounded_corner == other.is_fast_rounded_corner && // The specific reason is just for tracing/testing/debugging, so just @@ -67,7 +67,9 @@ bool EffectNode::operator==(const EffectNode& other) const { hidden_by_backface_visibility == other.hidden_by_backface_visibility && double_sided == other.double_sided && trilinear_filtering == other.trilinear_filtering && - is_drawn == other.is_drawn && subtree_hidden == other.subtree_hidden && + is_drawn == other.is_drawn && + only_draws_visible_content == other.only_draws_visible_content && + subtree_hidden == other.subtree_hidden && has_potential_filter_animation == other.has_potential_filter_animation && has_potential_backdrop_filter_animation == @@ -163,6 +165,7 @@ void EffectNode::AsValueInto(base::trace_event::TracedValue* value) const { hidden_by_backface_visibility); value->SetBoolean("trilinear_filtering", trilinear_filtering); value->SetBoolean("is_drawn", is_drawn); + value->SetBoolean("only_draws_visible_content", only_draws_visible_content); value->SetBoolean("has_potential_filter_animation", has_potential_filter_animation); value->SetBoolean("has_potential_backdrop_filter_animation", diff --git a/chromium/cc/trees/effect_node.h b/chromium/cc/trees/effect_node.h index 01f662563e1..6663457250c 100644 --- a/chromium/cc/trees/effect_node.h +++ b/chromium/cc/trees/effect_node.h @@ -96,6 +96,13 @@ struct CC_EXPORT EffectNode { bool double_sided : 1; bool trilinear_filtering : 1; bool is_drawn : 1; + // In most cases we only need to draw the visible part of any content + // contributing to the effect. For copy request case, we would need to copy + // the entire content, and could not only draw the visible part. In the rare + // case of a backdrop zoom filter we need to take into consideration the + // content offscreen to make sure the backdrop zoom filter is applied with the + // correct center. + bool only_draws_visible_content : 1; // TODO(jaydasika) : Delete this after implementation of // SetHideLayerAndSubtree is cleaned up. (crbug.com/595843) bool subtree_hidden : 1; diff --git a/chromium/cc/trees/frame_rate_counter.cc b/chromium/cc/trees/frame_rate_counter.cc deleted file mode 100644 index 816877c4ab3..00000000000 --- a/chromium/cc/trees/frame_rate_counter.cc +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright 2012 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/frame_rate_counter.h" - -#include <stddef.h> - -#include <algorithm> -#include <limits> - -#include "base/memory/ptr_util.h" -#include "base/metrics/histogram_macros.h" - -namespace cc { - -// The following constants are measured in seconds. - -// Two thresholds (measured in seconds) that describe what is considered to be a -// "no-op frame" that should not be counted. -// - if the frame is too fast, then given our compositor implementation, the -// frame probably was a no-op and did not draw. -// - if the frame is too slow, then there is probably not animating content, so -// we should not pollute the average. -static const double kFrameTooFast = 1.0 / 70.0; -static const double kFrameTooSlow = 1.5; - -// If a frame takes longer than this threshold (measured in seconds) then we -// (naively) assume that it missed a screen refresh; that is, we dropped a -// frame. -// TODO(brianderson): Determine this threshold based on monitor refresh rate, -// crbug.com/138642. -static const double kDroppedFrameTime = 1.0 / 50.0; - -// static -std::unique_ptr<FrameRateCounter> FrameRateCounter::Create( - bool has_impl_thread) { - return base::WrapUnique(new FrameRateCounter(has_impl_thread)); -} - -base::TimeDelta FrameRateCounter::RecentFrameInterval(size_t n) const { - DCHECK_GT(n, 0u); - DCHECK_LT(n, ring_buffer_.BufferSize()); - return ring_buffer_.ReadBuffer(n) - ring_buffer_.ReadBuffer(n - 1); -} - -FrameRateCounter::FrameRateCounter(bool has_impl_thread) - : has_impl_thread_(has_impl_thread), dropped_frame_count_(0) {} - -void FrameRateCounter::SaveTimeStamp(base::TimeTicks timestamp) { - ring_buffer_.SaveToBuffer(timestamp); - - // Check if frame interval can be computed. - if (ring_buffer_.CurrentIndex() < 2) - return; - - base::TimeDelta frame_interval_seconds = - RecentFrameInterval(ring_buffer_.BufferSize() - 1); - - if (!IsBadFrameInterval(frame_interval_seconds) && - frame_interval_seconds.InSecondsF() > kDroppedFrameTime) - dropped_frame_count_ += - frame_interval_seconds.InSecondsF() / kDroppedFrameTime; -} - -bool FrameRateCounter::IsBadFrameInterval( - base::TimeDelta interval_between_consecutive_frames) const { - double delta = interval_between_consecutive_frames.InSecondsF(); - bool scheduler_allows_double_frames = !has_impl_thread_; - bool interval_too_fast = - scheduler_allows_double_frames ? delta < kFrameTooFast : delta <= 0.0; - bool interval_too_slow = delta > kFrameTooSlow; - return interval_too_fast || interval_too_slow; -} - -void FrameRateCounter::GetMinAndMaxFPS(double* min_fps, double* max_fps) const { - *min_fps = std::numeric_limits<double>::max(); - *max_fps = 0.0; - - for (RingBufferType::Iterator it = --ring_buffer_.End(); it; --it) { - base::TimeDelta delta = RecentFrameInterval(it.index() + 1); - - if (IsBadFrameInterval(delta)) - continue; - - DCHECK_GT(delta.InSecondsF(), 0.f); - double fps = 1.0 / delta.InSecondsF(); - - *min_fps = std::min(fps, *min_fps); - *max_fps = std::max(fps, *max_fps); - } - - if (*min_fps > *max_fps) - *min_fps = *max_fps; -} - -double FrameRateCounter::GetAverageFPS() const { - int frame_count = 0; - double frame_times_total = 0.0; - double average_fps = 0.0; - - // Walk backwards through the samples looking for a run of good frame - // timings from which to compute the mean. - // - // Slow frames occur just because the user is inactive, and should be - // ignored. Fast frames are ignored if the scheduler is in single-thread - // mode in order to represent the true frame rate in spite of the fact that - // the first few swapbuffers happen instantly which skews the statistics - // too much for short lived animations. - // - // IsBadFrameInterval encapsulates the frame too slow/frame too fast logic. - - for (RingBufferType::Iterator it = --ring_buffer_.End(); - it && frame_times_total < 1.0; --it) { - base::TimeDelta delta = RecentFrameInterval(it.index() + 1); - - if (!IsBadFrameInterval(delta)) { - frame_count++; - frame_times_total += delta.InSecondsF(); - } else if (frame_count) { - break; - } - } - - if (frame_count) { - DCHECK_GT(frame_times_total, 0.0); - average_fps = frame_count / frame_times_total; - } - - return average_fps; -} - -} // namespace cc diff --git a/chromium/cc/trees/frame_rate_counter.h b/chromium/cc/trees/frame_rate_counter.h deleted file mode 100644 index f1445fb16da..00000000000 --- a/chromium/cc/trees/frame_rate_counter.h +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2012 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_FRAME_RATE_COUNTER_H_ -#define CC_TREES_FRAME_RATE_COUNTER_H_ - -#include <stddef.h> - -#include <memory> - -#include "base/containers/ring_buffer.h" -#include "base/time/time.h" - -namespace cc { - -// This class maintains a history of timestamps, and provides functionality to -// intelligently compute average frames per second. -class FrameRateCounter { - public: - static std::unique_ptr<FrameRateCounter> Create(bool has_impl_thread); - - FrameRateCounter(const FrameRateCounter&) = delete; - FrameRateCounter& operator=(const FrameRateCounter&) = delete; - - size_t current_frame_number() const { return ring_buffer_.CurrentIndex(); } - int dropped_frame_count() const { return dropped_frame_count_; } - size_t time_stamp_history_size() const { return ring_buffer_.BufferSize(); } - - void SaveTimeStamp(base::TimeTicks timestamp); - - // n = 0 returns the oldest frame interval retained in the history, while n = - // time_stamp_history_size() - 1 returns the most recent frame interval. - base::TimeDelta RecentFrameInterval(size_t n) const; - - // This is a heuristic that can be used to ignore frames in a reasonable way. - // Returns true if the given frame interval is too fast or too slow, based on - // constant thresholds. - bool IsBadFrameInterval( - base::TimeDelta interval_between_consecutive_frames) const; - - void GetMinAndMaxFPS(double* min_fps, double* max_fps) const; - double GetAverageFPS() const; - - typedef base::RingBuffer<base::TimeTicks, 136> RingBufferType; - RingBufferType::Iterator begin() const { return ring_buffer_.Begin(); } - RingBufferType::Iterator end() const { return ring_buffer_.End(); } - - private: - explicit FrameRateCounter(bool has_impl_thread); - - RingBufferType ring_buffer_; - - bool has_impl_thread_; - int dropped_frame_count_; -}; - -} // namespace cc - -#endif // CC_TREES_FRAME_RATE_COUNTER_H_ diff --git a/chromium/cc/trees/image_animation_controller.cc b/chromium/cc/trees/image_animation_controller.cc index a7e1ebf6de6..2cb4f9d1bd1 100644 --- a/chromium/cc/trees/image_animation_controller.cc +++ b/chromium/cc/trees/image_animation_controller.cc @@ -4,6 +4,8 @@ #include "cc/trees/image_animation_controller.h" +#include <sstream> + #include "base/bind.h" #include "base/metrics/histogram_macros.h" #include "base/trace_event/trace_event.h" @@ -360,6 +362,25 @@ bool ImageAnimationController::AnimationState::AdvanceFrame( // skipped trying to catch up. DCHECK_GT(num_of_frames_advanced, 0u); last_num_frames_skipped_ = num_of_frames_advanced - 1u; + switch (repetitions_completed_) { + case 0: + UMA_HISTOGRAM_COUNTS_100000( + "AnimatedImage.NumOfFramesSkipped.FirstAnimationLoop", + last_num_frames_skipped_); + break; + case 1: + UMA_HISTOGRAM_COUNTS_100000( + "AnimatedImage.NumOfFramesSkipped.SecondAnimationLoop", + last_num_frames_skipped_); + break; + case 2: + case 3: + case 4: + UMA_HISTOGRAM_COUNTS_100000( + "AnimatedImage.NumOfFramesSkipped.ThirdToFifthAnimationLoop", + last_num_frames_skipped_); + break; + } UMA_HISTOGRAM_COUNTS_100000("AnimatedImage.NumOfFramesSkipped.Compositor", last_num_frames_skipped_); return needs_invalidation(); diff --git a/chromium/cc/trees/layer_tree_host.cc b/chromium/cc/trees/layer_tree_host.cc index b1a4798682b..d5e57eaa4a5 100644 --- a/chromium/cc/trees/layer_tree_host.cc +++ b/chromium/cc/trees/layer_tree_host.cc @@ -18,6 +18,7 @@ #include "base/command_line.h" #include "base/containers/adapters.h" #include "base/location.h" +#include "base/logging.h" #include "base/memory/ptr_util.h" #include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_macros.h" @@ -94,6 +95,9 @@ LayerTreeHost::InitParams::InitParams(InitParams&&) = default; LayerTreeHost::InitParams& LayerTreeHost::InitParams::operator=(InitParams&&) = default; +LayerTreeHost::ScrollAnimationState::ScrollAnimationState() = default; +LayerTreeHost::ScrollAnimationState::~ScrollAnimationState() = default; + std::unique_ptr<LayerTreeHost> LayerTreeHost::CreateThreaded( scoped_refptr<base::SingleThreadTaskRunner> impl_task_runner, InitParams params) { @@ -263,14 +267,6 @@ const LayerTreeSettings& LayerTreeHost::GetSettings() const { void LayerTreeHost::QueueSwapPromise( std::unique_ptr<SwapPromise> swap_promise) { swap_promise_manager_.QueueSwapPromise(std::move(swap_promise)); - - // Request a main frame if one is not already in progress. This might either - // A) request a commit ahead of time or B) request a commit which is not - // needed because there are not pending updates. If B) then the frame will - // be aborted early and the swap promises will be broken (see - // EarlyOut_NoUpdates). - if (!inside_main_frame_) - SetNeedsAnimate(); } void LayerTreeHost::WillBeginMainFrame() { @@ -609,6 +605,11 @@ void LayerTreeHost::SetNeedsAnimate() { events_metrics_manager_.SaveActiveEventMetrics(); } +void LayerTreeHost::SetNeedsAnimateIfNotInsideMainFrame() { + if (!inside_main_frame_) + SetNeedsAnimate(); +} + DISABLE_CFI_PERF void LayerTreeHost::SetNeedsUpdateLayers() { proxy_->SetNeedsUpdateLayers(); @@ -785,6 +786,13 @@ bool LayerTreeHost::CaptureContent(std::vector<NodeId>* content) { return true; } +void LayerTreeHost::DidObserveFirstScrollDelay( + base::TimeDelta first_scroll_delay, + base::TimeTicks first_scroll_timestamp) { + client_->DidObserveFirstScrollDelay(first_scroll_delay, + first_scroll_timestamp); +} + bool LayerTreeHost::DoUpdateLayers() { TRACE_EVENT1("cc,benchmark", "LayerTreeHost::DoUpdateLayers", "source_frame_number", SourceFrameNumber()); @@ -849,6 +857,24 @@ void LayerTreeHost::ApplyViewportChanges(const ScrollAndScaleSet& info) { if (info.inner_viewport_scroll.element_id) inner_viewport_scroll_delta = info.inner_viewport_scroll.scroll_delta; + // When a new scroll-animation starts, it is necessary to check + // |info.manipulation_info| to make sure the scroll-animation was started by + // an input event. + // If there is already an ongoing scroll-animation, then it is necessary to + // only look at |info.ongoing_scroll_animation| (since it is possible for the + // scroll-animation to continue even if no event was handled). + bool new_ongoing_scroll = + scroll_animation_.in_progress + ? info.ongoing_scroll_animation + : (info.ongoing_scroll_animation && info.manipulation_info); + if (scroll_animation_.in_progress && !new_ongoing_scroll) { + scroll_animation_.in_progress = false; + if (!scroll_animation_.end_notification.is_null()) + std::move(scroll_animation_.end_notification).Run(); + } else { + scroll_animation_.in_progress = new_ongoing_scroll; + } + if (inner_viewport_scroll_delta.IsZero() && info.page_scale_delta == 1.f && info.elastic_overscroll_delta.IsZero() && !info.top_controls_delta && !info.bottom_controls_delta && @@ -985,6 +1011,14 @@ void LayerTreeHost::NotifyThroughputTrackerResults( client_->NotifyThroughputTrackerResults(std::move(results)); } +void LayerTreeHost::SubmitThroughputData(ukm::SourceId source_id, + int aggregated_percent, + int impl_percent, + base::Optional<int> main_percent) { + client_->SubmitThroughputData(source_id, aggregated_percent, impl_percent, + main_percent); +} + const base::WeakPtr<InputHandler>& LayerTreeHost::GetInputHandler() const { return input_handler_weak_ptr_; } @@ -1068,6 +1102,15 @@ void LayerTreeHost::RequestPresentationTimeForNextFrame( pending_presentation_time_callbacks_.push_back(std::move(callback)); } +void LayerTreeHost::RequestScrollAnimationEndNotification( + base::OnceClosure callback) { + DCHECK(scroll_animation_.end_notification.is_null()); + if (scroll_animation_.in_progress) + scroll_animation_.end_notification = std::move(callback); + else + std::move(callback).Run(); +} + void LayerTreeHost::SetRootLayer(scoped_refptr<Layer> root_layer) { if (root_layer_.get() == root_layer.get()) return; @@ -1118,9 +1161,13 @@ Layer* LayerTreeHost::InnerViewportScrollLayerForTesting() const { } Layer* LayerTreeHost::OuterViewportScrollLayerForTesting() const { + return LayerByElementId(OuterViewportScrollElementId()); +} + +ElementId LayerTreeHost::OuterViewportScrollElementId() const { auto* scroll_node = property_trees()->scroll_tree.Node(viewport_property_ids_.outer_scroll); - return scroll_node ? LayerByElementId(scroll_node->element_id) : nullptr; + return scroll_node ? scroll_node->element_id : ElementId(); } void LayerTreeHost::RegisterSelection(const LayerSelection& selection) { @@ -1439,10 +1486,6 @@ void LayerTreeHost::AddLayerShouldPushProperties(Layer* layer) { layers_that_should_push_properties_.insert(layer); } -void LayerTreeHost::RemoveLayerShouldPushProperties(Layer* layer) { - layers_that_should_push_properties_.erase(layer); -} - void LayerTreeHost::ClearLayersThatShouldPushProperties() { layers_that_should_push_properties_.clear(); } @@ -1566,6 +1609,9 @@ void LayerTreeHost::PushLayerTreePropertiesTo(LayerTreeImpl* tree_impl) { } tree_impl->set_display_transform_hint(display_transform_hint_); + + if (delegated_ink_metadata_) + tree_impl->set_delegated_ink_metadata(std::move(delegated_ink_metadata_)); } void LayerTreeHost::PushSurfaceRangesTo(LayerTreeImpl* tree_impl) { diff --git a/chromium/cc/trees/layer_tree_host.h b/chromium/cc/trees/layer_tree_host.h index 8aae435b230..f365bd9a909 100644 --- a/chromium/cc/trees/layer_tree_host.h +++ b/chromium/cc/trees/layer_tree_host.h @@ -13,6 +13,7 @@ #include <set> #include <string> #include <unordered_map> +#include <utility> #include <vector> #include "base/callback_forward.h" @@ -49,6 +50,7 @@ #include "cc/trees/swap_promise_manager.h" #include "cc/trees/target_property.h" #include "cc/trees/viewport_layers.h" +#include "components/viz/common/delegated_ink_metadata.h" #include "components/viz/common/resources/resource_format.h" #include "components/viz/common/surfaces/local_surface_id_allocation.h" #include "services/metrics/public/cpp/ukm_source_id.h" @@ -215,6 +217,9 @@ class CC_EXPORT LayerTreeHost : public MutatorHostClient { // full commit synchronization or layer updates. void SetNeedsAnimate(); + // Calls SetNeedsAnimate() if there is no main frame already in progress. + void SetNeedsAnimateIfNotInsideMainFrame(); + // Requests a main frame update and also ensure that the host pulls layer // updates from the client, even if no content might have changed, without // forcing a full commit synchronization. @@ -316,6 +321,10 @@ class CC_EXPORT LayerTreeHost : public MutatorHostClient { base::OnceCallback<void(const gfx::PresentationFeedback&)>; void RequestPresentationTimeForNextFrame(PresentationTimeCallback callback); + // Registers a callback that is run when any ongoing scroll-animation ends. If + // there are no ongoing animations, then the callback is run immediately. + void RequestScrollAnimationEndNotification(base::OnceClosure callback); + // Layer tree accessors and modifiers ------------------------ // Sets or gets the root of the Layer tree. Children of the root Layer are @@ -352,6 +361,8 @@ class CC_EXPORT LayerTreeHost : public MutatorHostClient { Layer* InnerViewportScrollLayerForTesting() const; Layer* OuterViewportScrollLayerForTesting() const; + ElementId OuterViewportScrollElementId() const; + // Sets or gets the position of touch handles for a text selection. These are // submitted to the display compositor along with the Layer tree's contents // allowing it to present the selection handles. This is done because the @@ -388,8 +399,6 @@ class CC_EXPORT LayerTreeHost : public MutatorHostClient { void SetViewportVisibleRect(const gfx::Rect& visible_rect); - gfx::Rect viewport_visible_rect() const { return viewport_visible_rect_; } - gfx::Rect device_viewport_rect() const { return device_viewport_rect_; } void SetBrowserControlsParams(const BrowserControlsParams& params); @@ -457,6 +466,10 @@ class CC_EXPORT LayerTreeHost : public MutatorHostClient { return raster_color_space_; } + bool HasCompositorDrivenScrollAnimationForTesting() const { + return scroll_animation_.in_progress; + } + // This layer tree may be embedded in a hierarchy that has page scale // factor controlled at the top level. We represent that scale here as // 'external_page_scale_factor', a value that affects raster scale in the @@ -503,7 +516,6 @@ class CC_EXPORT LayerTreeHost : public MutatorHostClient { // These are internal methods, called from the Layer itself when changing a // property or completing a PushPropertiesTo. void AddLayerShouldPushProperties(Layer* layer); - void RemoveLayerShouldPushProperties(Layer* layer); void ClearLayersThatShouldPushProperties(); // The current set of all Layers attached to the LayerTreeHost's tree that // have been marked as needing PushPropertiesTo in the next commit. @@ -593,6 +605,10 @@ class CC_EXPORT LayerTreeHost : public MutatorHostClient { void RecordEndOfFrameMetrics(base::TimeTicks frame_begin_time, ActiveFrameSequenceTrackers trackers); void NotifyThroughputTrackerResults(CustomTrackerResults results); + void SubmitThroughputData(ukm::SourceId source_id, + int aggregated_percent, + int impl_percent, + base::Optional<int> main_percent); LayerTreeHostClient* client() { return client_; } LayerTreeHostSchedulingClient* scheduling_client() { @@ -693,6 +709,17 @@ class CC_EXPORT LayerTreeHost : public MutatorHostClient { impl_commit_start_time_ = commit_start_time; } + void SetDelegatedInkMetadata( + std::unique_ptr<viz::DelegatedInkMetadata> metadata) { + delegated_ink_metadata_ = std::move(metadata); + } + viz::DelegatedInkMetadata* DelegatedInkMetadataForTesting() { + return delegated_ink_metadata_.get(); + } + + void DidObserveFirstScrollDelay(base::TimeDelta first_scroll_delay, + base::TimeTicks first_scroll_timestamp); + protected: LayerTreeHost(InitParams params, CompositorMode mode); @@ -886,6 +913,17 @@ class CC_EXPORT LayerTreeHost : public MutatorHostClient { // added here. std::vector<PresentationTimeCallback> pending_presentation_time_callbacks_; + struct ScrollAnimationState { + ScrollAnimationState(); + ~ScrollAnimationState(); + + // Tracks whether there is an ongoing compositor-driven scroll animation. + bool in_progress = false; + + // Callback to run when the scroll-animation ends. + base::OnceClosure end_notification; + } scroll_animation_; + // Latency information for work done in ProxyMain::BeginMainFrame. The // unique_ptr is allocated in RequestMainFrameUpdate, and passed to Blink's // LocalFrameView that fills in the fields. This object adds the timing for @@ -899,6 +937,12 @@ class CC_EXPORT LayerTreeHost : public MutatorHostClient { EventsMetricsManager events_metrics_manager_; + // Metadata required for drawing a delegated ink trail onto the end of a + // stroke. std::unique_ptr was specifically chosen so that it would be cleared + // as it is forwarded along the pipeline to avoid old information incorrectly + // sticking around and potentially being reused. + std::unique_ptr<viz::DelegatedInkMetadata> delegated_ink_metadata_; + // Used to vend weak pointers to LayerTreeHost to ScopedDeferMainFrameUpdate // objects. base::WeakPtrFactory<LayerTreeHost> defer_main_frame_update_weak_ptr_factory_{ diff --git a/chromium/cc/trees/layer_tree_host_client.h b/chromium/cc/trees/layer_tree_host_client.h index ae48bd94a18..b4b8c607969 100644 --- a/chromium/cc/trees/layer_tree_host_client.h +++ b/chromium/cc/trees/layer_tree_host_client.h @@ -11,6 +11,7 @@ #include "base/time/time.h" #include "cc/input/browser_controls_state.h" #include "cc/metrics/frame_sequence_tracker_collection.h" +#include "services/metrics/public/cpp/ukm_source_id.h" #include "ui/gfx/geometry/scroll_offset.h" #include "ui/gfx/geometry/vector2d_f.h" @@ -101,6 +102,9 @@ class LayerTreeHostClient { virtual void WillUpdateLayers() = 0; virtual void DidUpdateLayers() = 0; + virtual void DidObserveFirstScrollDelay( + base::TimeDelta first_scroll_delay, + base::TimeTicks first_scroll_timestamp) = 0; // Notification that the proxy started or stopped deferring main frame updates virtual void OnDeferMainFrameUpdatesChanged(bool) = 0; @@ -169,6 +173,12 @@ class LayerTreeHostClient { // RecordEndOfFrameMetrics. virtual std::unique_ptr<BeginMainFrameMetrics> GetBeginMainFrameMetrics() = 0; virtual void NotifyThroughputTrackerResults(CustomTrackerResults results) = 0; + // LayerTreeHost calls this when there is new throughput data. The client send + // the data to the browser process who will aggregate them and report to UKM. + virtual void SubmitThroughputData(ukm::SourceId source_id, + int aggregated_percent, + int impl_percent, + base::Optional<int> main_percent) = 0; protected: virtual ~LayerTreeHostClient() = default; diff --git a/chromium/cc/trees/layer_tree_host_impl.cc b/chromium/cc/trees/layer_tree_host_impl.cc index 4fcc3210311..0fbcdf35620 100644 --- a/chromium/cc/trees/layer_tree_host_impl.cc +++ b/chromium/cc/trees/layer_tree_host_impl.cc @@ -20,6 +20,7 @@ #include "base/debug/crash_logging.h" #include "base/debug/dump_without_crashing.h" #include "base/location.h" +#include "base/logging.h" #include "base/memory/ptr_util.h" #include "base/memory/read_only_shared_memory_region.h" #include "base/metrics/histogram.h" @@ -30,6 +31,7 @@ #include "base/trace_event/traced_value.h" #include "build/build_config.h" #include "cc/base/devtools_instrumentation.h" +#include "cc/base/features.h" #include "cc/base/histograms.h" #include "cc/base/math_util.h" #include "cc/base/switches.h" @@ -40,6 +42,7 @@ #include "cc/input/page_scale_animation.h" #include "cc/input/scroll_elasticity_helper.h" #include "cc/input/scroll_state.h" +#include "cc/input/scroll_utils.h" #include "cc/input/scrollbar.h" #include "cc/input/scrollbar_animation_controller.h" #include "cc/input/scroller_size_metrics.h" @@ -79,7 +82,6 @@ #include "cc/trees/debug_rect_history.h" #include "cc/trees/draw_property_utils.h" #include "cc/trees/effect_node.h" -#include "cc/trees/frame_rate_counter.h" #include "cc/trees/image_animation_controller.h" #include "cc/trees/latency_info_swap_promise_monitor.h" #include "cc/trees/layer_tree_frame_sink.h" @@ -125,6 +127,7 @@ #include "ui/gfx/geometry/scroll_offset.h" #include "ui/gfx/geometry/size_conversions.h" #include "ui/gfx/geometry/vector2d_conversions.h" +#include "ui/gfx/geometry/vector2d_f.h" #include "ui/gfx/skia_util.h" namespace cc { @@ -248,6 +251,41 @@ void PopulateMetadataContentColorUsage( } } +// Registers callbacks, as needed, to track First Scroll Latency. +void ApplyFirstScrollTracking(const ui::LatencyInfo* latency, + uint32_t frame_token, + LayerTreeHostImpl* impl) { + base::TimeTicks creation_timestamp; + // If |latency| isn't tracking a scroll, we don't need to do extra + // first-scroll tracking. + auto scroll_update_started = + ui::INPUT_EVENT_LATENCY_FIRST_SCROLL_UPDATE_ORIGINAL_COMPONENT; + auto scroll_update_continued = + ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT; + if (!latency->FindLatency(scroll_update_started, &creation_timestamp) && + !latency->FindLatency(scroll_update_continued, &creation_timestamp)) { + return; + } + + // Construct a callback that, given presentation feedback, will report the + // time span between the scroll input-event creation and the + // presentation timestamp. + LayerTreeHost::PresentationTimeCallback presentation_callback = + base::BindOnce( + [](base::TimeTicks event_creation, + LayerTreeHostImpl* layer_tree_host_impl, + const gfx::PresentationFeedback& feedback) { + layer_tree_host_impl->DidObserveScrollDelay( + feedback.timestamp - event_creation, event_creation); + }, + creation_timestamp, impl); + + // Tell the |LayerTreeHostImpl| to run our callback with the presentation + // feedback corresponding to the given |frame_token|. + impl->RegisterCompositorPresentationTimeCallback( + frame_token, std::move(presentation_callback)); +} + // These values are persisted to logs. Entries should not be renumbered and // numeric values should never be reused. enum class SourceIdConsistency : int { @@ -333,8 +371,6 @@ LayerTreeHostImpl::LayerTreeHostImpl( ? std::numeric_limits<size_t>::max() : settings.scheduled_raster_task_limit, settings.ToTileManagerSettings()), - fps_counter_( - FrameRateCounter::Create(task_runner_provider_->HasImplThread())), memory_history_(MemoryHistory::Create()), debug_rect_history_(DebugRectHistory::Create()), mutator_host_(std::move(mutator_host)), @@ -379,12 +415,14 @@ LayerTreeHostImpl::LayerTreeHostImpl( if (!base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kDisableLayerTreeHostMemoryPressure)) { - memory_pressure_listener_.reset( - new base::MemoryPressureListener(base::BindRepeating( - &LayerTreeHostImpl::OnMemoryPressure, base::Unretained(this)))); + memory_pressure_listener_ = std::make_unique<base::MemoryPressureListener>( + FROM_HERE, base::BindRepeating(&LayerTreeHostImpl::OnMemoryPressure, + base::Unretained(this))); } SetDebugState(settings.initial_debug_state); + compositor_frame_reporting_controller_->SetDroppedFrameCounter( + &dropped_frame_counter_); } LayerTreeHostImpl::~LayerTreeHostImpl() { @@ -428,7 +466,6 @@ LayerTreeHostImpl::~LayerTreeHostImpl() { // Clear the UKM Manager so that we do not try to report when the // UKM System has shut down. compositor_frame_reporting_controller_->SetUkmManager(nullptr); - frame_trackers_.SetUkmManager(nullptr); } void LayerTreeHostImpl::WillSendBeginMainFrame() { @@ -1034,6 +1071,7 @@ bool LayerTreeHostImpl::ScrollLayerTo(ElementId element_id, } bool LayerTreeHostImpl::ScrollingShouldSwitchtoMainThread() { + DCHECK(!base::FeatureList::IsEnabled(features::kScrollUnification)); ScrollTree& scroll_tree = active_tree_->property_trees()->scroll_tree; ScrollNode* scroll_node = scroll_tree.CurrentlyScrollingNode(); @@ -1550,8 +1588,9 @@ DrawResult LayerTreeHostImpl::PrepareToDraw(FrameData* frame) { base::saturated_cast<int>(active_tree_->picture_layers().size()), 1, 400, 20); - // TODO(yigu): Maybe we should use the same check above. Need to figure out - // why exactly we skip 0. + // TODO(pdr): Instead of skipping empty picture layers, maybe we should + // accumulate layer->GetRasterSource()->GetMemoryUsage() above and skip + // recording when the accumulated memory usage is 0. if (!active_tree()->picture_layers().empty()) { UMA_HISTOGRAM_CUSTOM_COUNTS( base::StringPrintf("Compositing.%s.GPUMemoryForTilingsInKb", @@ -1719,7 +1758,7 @@ void LayerTreeHostImpl::ResetTreesForTesting() { } size_t LayerTreeHostImpl::SourceAnimationFrameNumberForTesting() const { - return fps_counter_->current_frame_number(); + return *next_frame_token_; } void LayerTreeHostImpl::UpdateTileManagerMemoryPolicy( @@ -2192,6 +2231,15 @@ viz::CompositorFrameMetadata LayerTreeHostImpl::MakeCompositorFrameMetadata() { metadata.display_transform_hint = active_tree_->display_transform_hint(); + if (std::unique_ptr<viz::DelegatedInkMetadata> delegated_ink_metadata = + active_tree_->take_delegated_ink_metadata()) { + TRACE_EVENT_INSTANT1( + "cc", "Delegated Ink Metadata set on compositor frame metadata", + TRACE_EVENT_SCOPE_THREAD, "point", + delegated_ink_metadata->point().ToString()); + metadata.delegated_ink_metadata = std::move(delegated_ink_metadata); + } + return metadata; } @@ -2324,6 +2372,19 @@ bool LayerTreeHostImpl::DrawLayers(FrameData* frame) { std::move(compositor_frame), /*hit_test_data_changed=*/false, debug_state_.show_hit_test_borders); + // This is expected to be true roughly every 5 seconds. + if (frame_trackers_.HasThroughputData()) { + ukm::SourceId source_id = ukm_manager_->source_id(); + // source_id can be invalid in tests. + if (source_id != ukm::kInvalidSourceId) { + int aggregated_percent = frame_trackers_.TakeLastAggregatedPercent(); + int impl_percent = frame_trackers_.TakeLastImplPercent(); + base::Optional<int> main_percent = frame_trackers_.TakeLastMainPercent(); + client_->SubmitThroughputData(source_id, aggregated_percent, impl_percent, + main_percent); + } + } + #if DCHECK_IS_ON() if (!doing_sync_draw_) { // The throughput computation (in |FrameSequenceTracker|) depends on the @@ -2387,8 +2448,6 @@ viz::CompositorFrame LayerTreeHostImpl::GenerateCompositorFrame( TRACE_ID_GLOBAL(CurrentBeginFrameArgs().trace_id), TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "step", "GenerateCompositorFrame"); - base::TimeTicks frame_time = CurrentBeginFrameArgs().frame_time; - fps_counter_->SaveTimeStamp(frame_time); rendering_stats_instrumentation_->IncrementFrameCount(1); memory_history_->SaveEntry(tile_manager_.memory_stats_from_last_assign()); @@ -2457,6 +2516,8 @@ viz::CompositorFrame LayerTreeHostImpl::GenerateCompositorFrame( if (render_frame_metadata_observer_) { last_draw_render_frame_metadata_ = MakeRenderFrameMetadata(frame); + last_draw_render_frame_metadata_->has_delegated_ink_metadata = + metadata.delegated_ink_metadata.get(); // We cache the value of any new vertical scroll direction so that we can // accurately determine when the next change in vertical scroll direction @@ -2477,6 +2538,8 @@ viz::CompositorFrame LayerTreeHostImpl::GenerateCompositorFrame( base::TimeTicks draw_time = base::TimeTicks::Now(); ukm::SourceId exemplar = metadata.latency_info.front().ukm_source_id(); + ApplyFirstScrollTracking(&metadata.latency_info.front(), + metadata.frame_token, this); bool all_valid = true; bool all_unique = true; for (auto& latency : metadata.latency_info) { @@ -3653,6 +3716,16 @@ void LayerTreeHostImpl::DidChangeBrowserControlsPosition() { SetFullViewportDamage(); } +void LayerTreeHostImpl::DidObserveScrollDelay( + base::TimeDelta scroll_delay, + base::TimeTicks scroll_timestamp) { + // Record First Scroll Delay. + if (!has_observed_first_scroll_delay_) { + client_->DidObserveFirstScrollDelay(scroll_delay, scroll_timestamp); + has_observed_first_scroll_delay_ = true; + } +} + float LayerTreeHostImpl::TopControlsHeight() const { return active_tree_->top_controls_height(); } @@ -3690,20 +3763,43 @@ void LayerTreeHostImpl::BindToClient(InputHandlerClient* client) { input_handler_client_ = client; } -gfx::Vector2dF LayerTreeHostImpl::ResolveScrollPercentageToPixels( +gfx::Vector2dF LayerTreeHostImpl::ResolveScrollGranularityToPixels( const ScrollNode& scroll_node, - const gfx::Vector2dF& scroll_delta) { - gfx::Vector2dF scroll_delta_in_pixels; - scroll_delta_in_pixels.set_x(scroll_delta.x() * - scroll_node.container_bounds.width()); - scroll_delta_in_pixels.set_y(scroll_delta.y() * - scroll_node.container_bounds.height()); - return scroll_delta_in_pixels; + const gfx::Vector2dF& scroll_delta, + ui::ScrollGranularity granularity) { + gfx::Vector2dF pixel_delta = scroll_delta; + + if (granularity == ui::ScrollGranularity::kScrollByPage) { + // Page should use a percentage of the scroller so change the parameters + // and let the percentage case below resolve it. + granularity = ui::ScrollGranularity::kScrollByPercentage; + pixel_delta.Scale(kMinFractionToStepWhenPaging); + } + + if (granularity == ui::ScrollGranularity::kScrollByPercentage) { + gfx::SizeF scroller_size = gfx::SizeF(scroll_node.container_bounds); + + gfx::SizeF viewport_size = + InnerViewportScrollNode() + ? gfx::SizeF(InnerViewportScrollNode()->container_bounds) + : gfx::SizeF(active_tree()->GetDeviceViewport().size()); + + // Convert from rootframe coordinates to screen coordinates (physical + // pixels). + scroller_size.Scale(active_tree()->page_scale_factor_for_scroll()); + + pixel_delta = ScrollUtils::ResolveScrollPercentageToPixels( + scroll_delta, scroller_size, viewport_size); + } + + return pixel_delta; } InputHandler::ScrollStatus LayerTreeHostImpl::TryScroll( const ScrollTree& scroll_tree, ScrollNode* scroll_node) const { + DCHECK(!base::FeatureList::IsEnabled(features::kScrollUnification)); + InputHandler::ScrollStatus scroll_status; scroll_status.main_thread_scrolling_reasons = MainThreadScrollingReason::kNotScrollingOnMain; @@ -3736,9 +3832,9 @@ InputHandler::ScrollStatus LayerTreeHostImpl::TryScroll( } // If an associated scrolling layer is not found, the scroll node must not - // support impl-scrolling. The root, secondary root, and inner viewports are - // all exceptions to this and may not have a layer because it is not required - // for hit testing. + // support impl-scrolling. The root, secondary root, and inner viewports + // are all exceptions to this and may not have a layer because it is not + // required for hit testing. if (scroll_node->id != ScrollTree::kRootNodeId && scroll_node->id != ScrollTree::kSecondaryRootNodeId && !scroll_node->scrolls_inner_viewport && @@ -3804,6 +3900,7 @@ ScrollNode* LayerTreeHostImpl::FindScrollNodeForCompositedScrolling( LayerImpl* layer_impl, bool* scroll_on_main_thread, uint32_t* main_thread_scrolling_reasons) const { + DCHECK(!base::FeatureList::IsEnabled(features::kScrollUnification)); DCHECK(scroll_on_main_thread); DCHECK(main_thread_scrolling_reasons); *main_thread_scrolling_reasons = @@ -3894,7 +3991,13 @@ InputHandler::ScrollStatus LayerTreeHostImpl::RootScrollBegin( scroll_state->data()->set_current_native_scrolling_element( OuterViewportScrollNode()->element_id); - return ScrollBegin(scroll_state, type); + InputHandler::ScrollStatus scroll_status = ScrollBegin(scroll_state, type); + + // Since we provided an ElementId, there should never be a need to perform a + // hit test. + DCHECK(!scroll_status.needs_main_thread_hit_test); + + return scroll_status; } InputHandler::ScrollStatus LayerTreeHostImpl::ScrollBegin( @@ -3937,59 +4040,145 @@ InputHandler::ScrollStatus LayerTreeHostImpl::ScrollBegin( // this should only happen in ScrollEnd. We should DCHECK here that the state // is cleared instead. https://crbug.com/1016229 ClearCurrentlyScrollingNode(); + auto& scroll_tree = active_tree_->property_trees()->scroll_tree; - if (auto specified_element_id = - scroll_state->data()->current_native_scrolling_element()) { + ElementId target_element_id = scroll_state->target_element_id(); + + if (target_element_id && !scroll_state->is_main_thread_hit_tested()) { + TRACE_EVENT_INSTANT0("cc", "Latched scroll node provided", + TRACE_EVENT_SCOPE_THREAD); // If the caller passed in an element_id we can skip all the hit-testing // bits and provide a node straight-away. - auto& scroll_tree = active_tree_->property_trees()->scroll_tree; - scrolling_node = scroll_tree.FindNodeFromElementId(specified_element_id); - - // We still need to confirm the targeted node exists and can scroll on the - // compositor. - if (scrolling_node) { - scroll_status = TryScroll(active_tree_->property_trees()->scroll_tree, - scrolling_node); - if (IsMainThreadScrolling(scroll_status, scrolling_node)) - scroll_on_main_thread = true; + scrolling_node = scroll_tree.FindNodeFromElementId(target_element_id); + + // In unified scrolling, if we found a node we get to scroll it. + if (!base::FeatureList::IsEnabled(features::kScrollUnification)) { + // We still need to confirm the targeted node exists and can scroll on + // the compositor. + if (scrolling_node) { + scroll_status = TryScroll(active_tree_->property_trees()->scroll_tree, + scrolling_node); + if (IsMainThreadScrolling(scroll_status, scrolling_node)) + scroll_on_main_thread = true; + } } } else { - gfx::Point viewport_point(scroll_state->position_x(), - scroll_state->position_y()); + ScrollNode* starting_node = nullptr; + if (target_element_id) { + TRACE_EVENT_INSTANT0("cc", "Unlatched scroll node provided", + TRACE_EVENT_SCOPE_THREAD); + // We had an element id but we should still perform the walk up the + // scroll tree from the targeted node to latch to a scroller that can + // scroll in the given direction. This mode is only used when scroll + // unification is enabled and the targeted scroller comes back from a + // main thread hit test. + DCHECK(scroll_state->data()->is_main_thread_hit_tested); + DCHECK(base::FeatureList::IsEnabled(features::kScrollUnification)); + starting_node = scroll_tree.FindNodeFromElementId(target_element_id); + + if (!starting_node) { + // The main thread sent us an element_id that the compositor doesn't + // have a scroll node for. This can happen in some racy conditions, a + // freshly created scroller hasn't yet been committed or a + // scroller-destroying commit beats the hit test back to the compositor + // thread. However, these cases shouldn't be user perceptible. + scroll_status.main_thread_scrolling_reasons = + MainThreadScrollingReason::kNoScrollingLayer; + scroll_status.thread = SCROLL_IGNORED; + return scroll_status; + } + } else { + TRACE_EVENT_INSTANT0("cc", "Hit Testing for ScrollNode", + TRACE_EVENT_SCOPE_THREAD); + gfx::Point viewport_point(scroll_state->position_x(), + scroll_state->position_y()); + gfx::PointF device_viewport_point = gfx::ScalePoint( + gfx::PointF(viewport_point), active_tree_->device_scale_factor()); + + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + if (scroll_state->data()->is_main_thread_hit_tested) { + // The client should have discarded the scroll when the hit test came + // back with an invalid element id. If we somehow get here, we should + // drop the scroll as continuing could cause us to infinitely bounce + // back and forth between here and hit testing on the main thread. + NOTREACHED(); + scroll_status.main_thread_scrolling_reasons = + MainThreadScrollingReason::kNoScrollingLayer; + scroll_status.thread = SCROLL_IGNORED; + return scroll_status; + } - gfx::PointF device_viewport_point = gfx::ScalePoint( - gfx::PointF(viewport_point), active_tree_->device_scale_factor()); - LayerImpl* layer_impl = - active_tree_->FindLayerThatIsHitByPoint(device_viewport_point); + // Touch dragging the scrollbar requires falling back to main-thread + // scrolling. + // TODO(bokan): This could be trivially handled in the compositor by + // the new ScrollbarController and should be removed. + { + LayerImpl* first_scrolling_layer_or_scrollbar = + active_tree_->FindFirstScrollingLayerOrScrollbarThatIsHitByPoint( + device_viewport_point); + if (IsTouchDraggingScrollbar(first_scrolling_layer_or_scrollbar, + type)) { + TRACE_EVENT_INSTANT0("cc", "Scrollbar Scrolling", + TRACE_EVENT_SCOPE_THREAD); + scroll_status.thread = SCROLL_ON_MAIN_THREAD; + scroll_status.main_thread_scrolling_reasons = + MainThreadScrollingReason::kScrollbarScrolling; + return scroll_status; + } + } - if (layer_impl) { - LayerImpl* first_scrolling_layer_or_scrollbar = - active_tree_->FindFirstScrollingLayerOrScrollbarThatIsHitByPoint( - device_viewport_point); + ScrollHitTestResult scroll_hit_test = + HitTestScrollNode(device_viewport_point); + + if (!scroll_hit_test.hit_test_successful) { + // This result tells the client that the compositor doesn't have + // enough information to target this scroll. The client should + // perform a hit test in Blink and call this method again, with the + // ElementId of the hit-tested scroll node. + TRACE_EVENT_INSTANT0("cc", "Request Main Thread Hit Test", + TRACE_EVENT_SCOPE_THREAD); + scroll_status.thread = SCROLL_ON_IMPL_THREAD; + scroll_status.needs_main_thread_hit_test = true; + return scroll_status; + } - // Touch dragging the scrollbar requires falling back to main-thread - // scrolling. - if (IsTouchDraggingScrollbar(first_scrolling_layer_or_scrollbar, type)) { - TRACE_EVENT_INSTANT0("cc", "Scrollbar Scrolling", - TRACE_EVENT_SCOPE_THREAD); - scroll_status.thread = SCROLL_ON_MAIN_THREAD; - scroll_status.main_thread_scrolling_reasons = - MainThreadScrollingReason::kScrollbarScrolling; - return scroll_status; - } else if (!IsInitialScrollHitTestReliable( - layer_impl, first_scrolling_layer_or_scrollbar)) { - TRACE_EVENT_INSTANT0("cc", "Failed Hit Test", TRACE_EVENT_SCOPE_THREAD); - scroll_status.thread = SCROLL_UNKNOWN; - scroll_status.main_thread_scrolling_reasons = - MainThreadScrollingReason::kFailedHitTest; - return scroll_status; + starting_node = scroll_hit_test.scroll_node; + } else { + LayerImpl* layer_impl = + active_tree_->FindLayerThatIsHitByPoint(device_viewport_point); + + if (layer_impl) { + LayerImpl* first_scrolling_layer_or_scrollbar = + active_tree_->FindFirstScrollingLayerOrScrollbarThatIsHitByPoint( + device_viewport_point); + + // Touch dragging the scrollbar requires falling back to main-thread + // scrolling. + if (IsTouchDraggingScrollbar(first_scrolling_layer_or_scrollbar, + type)) { + TRACE_EVENT_INSTANT0("cc", "Scrollbar Scrolling", + TRACE_EVENT_SCOPE_THREAD); + scroll_status.thread = SCROLL_ON_MAIN_THREAD; + scroll_status.main_thread_scrolling_reasons = + MainThreadScrollingReason::kScrollbarScrolling; + return scroll_status; + } else if (!IsInitialScrollHitTestReliable( + layer_impl, first_scrolling_layer_or_scrollbar)) { + TRACE_EVENT_INSTANT0("cc", "Failed Hit Test", + TRACE_EVENT_SCOPE_THREAD); + scroll_status.thread = SCROLL_UNKNOWN; + scroll_status.main_thread_scrolling_reasons = + MainThreadScrollingReason::kFailedHitTest; + return scroll_status; + } + } + + starting_node = FindScrollNodeForCompositedScrolling( + device_viewport_point, layer_impl, &scroll_on_main_thread, + &scroll_status.main_thread_scrolling_reasons); } } - ScrollNode* starting_node = FindScrollNodeForCompositedScrolling( - device_viewport_point, layer_impl, &scroll_on_main_thread, - &scroll_status.main_thread_scrolling_reasons); - // The above finds the ScrollNode that's hit by the given point but we // still need to walk up the scroll tree looking for the first node that // can consume delta from the scroll state. @@ -3997,6 +4186,10 @@ InputHandler::ScrollStatus LayerTreeHostImpl::ScrollBegin( } if (scroll_on_main_thread) { + // Under scroll unification we can request a main thread hit test, but we + // should never send scrolls to the main thread. + DCHECK(!base::FeatureList::IsEnabled(features::kScrollUnification)); + RecordCompositorSlowScrollMetric(type, MAIN_THREAD); scroll_status.thread = SCROLL_ON_MAIN_THREAD; return scroll_status; @@ -4004,11 +4197,20 @@ InputHandler::ScrollStatus LayerTreeHostImpl::ScrollBegin( scroll_status.main_thread_scrolling_reasons = MainThreadScrollingReason::kNoScrollingLayer; if (settings_.is_layer_tree_for_subframe) { + // OOPIFs never have a viewport scroll node so if we can't scroll + // we need to be bubble up to the parent frame. This happens by + // returning SCROLL_UNKNOWN. TRACE_EVENT_INSTANT0("cc", "Ignored - No ScrollNode (OOPIF)", TRACE_EVENT_SCOPE_THREAD); scroll_status.thread = SCROLL_UNKNOWN; } else { - TRACE_EVENT_INSTANT0("cc", "Ignroed - No ScrollNode", + // If we didn't hit a layer above we'd usually fallback to the + // viewport scroll node. However, there may not be one if a scroll + // is received before the root layer has been attached. Chrome now + // drops input until the first commit is received so this probably + // can't happen in a typical browser session but there may still be + // configurations where input is allowed prior to a commit. + TRACE_EVENT_INSTANT0("cc", "Ignored - No ScrollNode", TRACE_EVENT_SCOPE_THREAD); scroll_status.thread = SCROLL_IGNORED; } @@ -4034,13 +4236,22 @@ InputHandler::ScrollStatus LayerTreeHostImpl::ScrollBegin( return scroll_status; } -ScrollNode* LayerTreeHostImpl::HitTestScrollNode( +LayerTreeHostImpl::ScrollHitTestResult LayerTreeHostImpl::HitTestScrollNode( const gfx::PointF& device_viewport_point) const { + ScrollHitTestResult result; + result.scroll_node = nullptr; + result.hit_test_successful = false; + LayerImpl* layer_impl = active_tree_->FindLayerThatIsHitByPoint(device_viewport_point); - if (!layer_impl) - return nullptr; + if (!layer_impl) { + result.hit_test_successful = true; + if (InnerViewportScrollNode()) + result.scroll_node = GetNodeToScroll(InnerViewportScrollNode()); + + return result; + } // There are some cases where the hit layer may not be correct (e.g. layer // squashing). If we detect this case, we can't target a scroll node here. @@ -4052,10 +4263,19 @@ ScrollNode* LayerTreeHostImpl::HitTestScrollNode( if (!IsInitialScrollHitTestReliable(layer_impl, first_scrolling_layer_or_scrollbar)) { TRACE_EVENT_INSTANT0("cc", "Failed Hit Test", TRACE_EVENT_SCOPE_THREAD); - return nullptr; + return result; } } + // If we hit a non-fast scrollable region, that means there's some reason we + // can't scroll in this region. Primarily, because there's another scroller + // there that isn't composited and we don't know about so we'll return + // nullptr in that case. + if (active_tree_->PointHitsNonFastScrollableRegion(device_viewport_point, + *layer_impl)) { + return result; + } + // If we hit a scrollbar layer, get the ScrollNode from its associated // scrolling layer, rather than directly from the scrollbar layer. The latter // would return the parent scroller's ScrollNode. @@ -4068,7 +4288,9 @@ ScrollNode* LayerTreeHostImpl::HitTestScrollNode( ScrollTree& scroll_tree = active_tree_->property_trees()->scroll_tree; ScrollNode* scroll_node = scroll_tree.Node(layer_impl->scroll_tree_index()); - return GetNodeToScroll(scroll_node); + result.scroll_node = GetNodeToScroll(scroll_node); + result.hit_test_successful = true; + return result; } // Requires falling back to main thread scrolling when it hit tests in scrollbar @@ -4602,10 +4824,9 @@ bool LayerTreeHostImpl::CanConsumeDelta(const ScrollState& scroll_state, return false; } delta_to_scroll = local_scroll_delta; - } else if (scroll_state.delta_granularity() == - ui::ScrollGranularity::kScrollByPercentage) { - delta_to_scroll = - ResolveScrollPercentageToPixels(scroll_node, delta_to_scroll); + } else { + delta_to_scroll = ResolveScrollGranularityToPixels( + scroll_node, delta_to_scroll, scroll_state.delta_granularity()); } if (ComputeScrollDelta(scroll_node, delta_to_scroll) != gfx::Vector2dF()) @@ -4656,21 +4877,27 @@ InputHandlerScrollResult LayerTreeHostImpl::ScrollUpdate( // The current_native_scrolling_element should only be set for ScrollBegin. DCHECK(!scroll_state->data()->current_native_scrolling_element()); + TRACE_EVENT2("cc", "LayerTreeHostImpl::ScrollUpdate", "dx", + scroll_state->delta_x(), "dy", scroll_state->delta_y()); if (!CurrentlyScrollingNode()) return InputHandlerScrollResult(); last_scroll_update_state_ = *scroll_state; - bool is_delta_percent_units = scroll_state->delta_granularity() == - ui::ScrollGranularity::kScrollByPercentage; - if (is_delta_percent_units) { - gfx::Vector2dF resolvedScrollDelta = ResolveScrollPercentageToPixels( - *CurrentlyScrollingNode(), - gfx::Vector2dF(scroll_state->delta_x(), scroll_state->delta_y())); - - scroll_state->data()->delta_x = resolvedScrollDelta.x(); - scroll_state->data()->delta_y = resolvedScrollDelta.y(); + gfx::Vector2dF resolvedScrollDelta = ResolveScrollGranularityToPixels( + *CurrentlyScrollingNode(), + gfx::Vector2dF(scroll_state->delta_x(), scroll_state->delta_y()), + scroll_state->delta_granularity()); + + scroll_state->data()->delta_x = resolvedScrollDelta.x(); + scroll_state->data()->delta_y = resolvedScrollDelta.y(); + // The decision of whether or not we'll animate a scroll comes down to + // whether the granularity is specified in precise pixels or not. Thus we + // need to preserve a precise granularity if that's what was specified; all + // others are animated and so can be resolved to regular pixels. + if (scroll_state->delta_granularity() != + ui::ScrollGranularity::kScrollByPrecisePixel) { scroll_state->data()->delta_granularity = ui::ScrollGranularity::kScrollByPixel; } @@ -4994,9 +5221,6 @@ void LayerTreeHostImpl::RecordScrollBegin( ScrollBeginThreadState scroll_start_state) { auto tracker_type = GetTrackerTypeForScroll(input_type); DCHECK_NE(tracker_type, FrameSequenceTrackerType::kMaxType); - auto* metrics = frame_trackers_.StartSequence(tracker_type); - if (!metrics) - return; // The main-thread is the 'scrolling thread' if: // (1) the scroll is driven by the main thread, or @@ -5015,7 +5239,7 @@ void LayerTreeHostImpl::RecordScrollBegin( scrolling_thread = FrameSequenceMetrics::ThreadType::kMain; break; } - metrics->SetScrollingThread(scrolling_thread); + frame_trackers_.StartScrollSequence(tracker_type, scrolling_thread); } void LayerTreeHostImpl::RecordScrollEnd(ui::ScrollInputType input_type) { @@ -5078,11 +5302,14 @@ InputHandlerPointerResult LayerTreeHostImpl::MouseMoveAt( gfx::PointF device_viewport_point = gfx::ScalePoint( gfx::PointF(viewport_point), active_tree_->device_scale_factor()); - ScrollNode* scroll_node = HitTestScrollNode(device_viewport_point); + + ScrollHitTestResult hit_test = HitTestScrollNode(device_viewport_point); + + ScrollNode* scroll_node = hit_test.scroll_node; // The hit test can fail in some cases, e.g. we don't know if a region of a // squashed layer has content or is empty. - if (!scroll_node) + if (!hit_test.hit_test_successful || !scroll_node) return result; // Scrollbars for the viewport are registered with the outer viewport layer. @@ -5242,6 +5469,9 @@ std::unique_ptr<ScrollAndScaleSet> LayerTreeHostImpl::ProcessScrollDeltas() { scroll_info->overscroll_delta = overscroll_delta_for_main_thread_; overscroll_delta_for_main_thread_ = gfx::Vector2dF(); + scroll_info->ongoing_scroll_animation = + !!mutator_host_->ImplOnlyScrollAnimatingElement(); + // Use the |last_latched_scroller_| rather than the |CurrentlyScrollingNode| // since the latter may be cleared by a GSE before we've committed these // values to the main thread. @@ -5398,9 +5628,10 @@ void LayerTreeHostImpl::RegisterScrollbarAnimationController( } void LayerTreeHostImpl::DidUnregisterScrollbarLayer( - ElementId scroll_element_id) { + ElementId scroll_element_id, + ScrollbarOrientation orientation) { scrollbar_animation_controllers_.erase(scroll_element_id); - scrollbar_controller_->DidUnregisterScrollbar(scroll_element_id); + scrollbar_controller_->DidUnregisterScrollbar(scroll_element_id, orientation); } ScrollbarAnimationController* @@ -6230,7 +6461,7 @@ void LayerTreeHostImpl::InitializeUkm( void LayerTreeHostImpl::SetActiveURL(const GURL& url, ukm::SourceId source_id) { tile_manager_.set_active_url(url); - + has_observed_first_scroll_delay_ = false; // The active tree might still be from content for the previous page when the // recorder is updated here, since new content will be pushed with the next // main frame. But we should only get a few impl frames wrong here in that diff --git a/chromium/cc/trees/layer_tree_host_impl.h b/chromium/cc/trees/layer_tree_host_impl.h index f7ce4e673e6..47dcba6decb 100644 --- a/chromium/cc/trees/layer_tree_host_impl.h +++ b/chromium/cc/trees/layer_tree_host_impl.h @@ -29,6 +29,7 @@ #include "cc/input/scrollbar_animation_controller.h" #include "cc/input/scrollbar_controller.h" #include "cc/layers/layer_collections.h" +#include "cc/metrics/dropped_frame_counter.h" #include "cc/metrics/event_metrics.h" #include "cc/metrics/events_metrics_manager.h" #include "cc/metrics/frame_sequence_tracker_collection.h" @@ -83,7 +84,6 @@ class BrowserControlsOffsetManager; class CompositorFrameReportingController; class DebugRectHistory; class EvictionTilePriorityQueue; -class FrameRateCounter; class ImageAnimationController; class LCDTextMetricsReporter; class LayerImpl; @@ -176,6 +176,17 @@ class LayerTreeHostImplClient { virtual void NotifyThroughputTrackerResults(CustomTrackerResults results) = 0; + // Send the throughput data to the main thread's LayerTreeHostClient, which + // then send the data to the browser process and eventually report to UKM. + virtual void SubmitThroughputData(ukm::SourceId source_id, + int aggregated_percent, + int impl_percent, + base::Optional<int> main_percent) = 0; + + virtual void DidObserveFirstScrollDelay( + base::TimeDelta first_scroll_delay, + base::TimeTicks first_scroll_timestamp) = 0; + protected: virtual ~LayerTreeHostImplClient() = default; }; @@ -333,6 +344,8 @@ class CC_EXPORT LayerTreeHostImpl : public InputHandler, float CurrentTopControlsShownRatio() const override; float CurrentBottomControlsShownRatio() const override; void DidChangeBrowserControlsPosition() override; + void DidObserveScrollDelay(base::TimeDelta scroll_delay, + base::TimeTicks scroll_timestamp); bool HaveRootScrollNode() const override; void SetNeedsCommit() override; @@ -469,7 +482,8 @@ class CC_EXPORT LayerTreeHostImpl : public InputHandler, void RegisterScrollbarAnimationController(ElementId scroll_element_id, float initial_opacity); - void DidUnregisterScrollbarLayer(ElementId scroll_element_id); + void DidUnregisterScrollbarLayer(ElementId scroll_element_id, + ScrollbarOrientation orientation); ScrollbarAnimationController* ScrollbarAnimationControllerForElementId( ElementId scroll_element_id) const; void FlashAllScrollbars(bool did_scroll); @@ -610,7 +624,8 @@ class CC_EXPORT LayerTreeHostImpl : public InputHandler, const ScrollNode* CurrentlyScrollingNode() const; bool scroll_affects_scroll_handler() const { - return scroll_affects_scroll_handler_; + return settings_.enable_synchronized_scrolling && + scroll_affects_scroll_handler_; } void QueueSwapPromiseForMainThreadScrollUpdate( std::unique_ptr<SwapPromise> swap_promise); @@ -641,9 +656,8 @@ class CC_EXPORT LayerTreeHostImpl : public InputHandler, const gfx::Transform& DrawTransform() const; std::unique_ptr<ScrollAndScaleSet> ProcessScrollDeltas(); - FrameRateCounter* fps_counter() { return fps_counter_.get(); } - base::Optional<int> current_universal_throughput() { - return frame_trackers_.current_universal_throughput(); + DroppedFrameCounter* dropped_frame_counter() { + return &dropped_frame_counter_; } MemoryHistory* memory_history() { return memory_history_.get(); } DebugRectHistory* debug_rect_history() { return debug_rect_history_.get(); } @@ -735,6 +749,13 @@ class CC_EXPORT LayerTreeHostImpl : public InputHandler, gfx::Vector2dF ComputeScrollDelta(const ScrollNode& scroll_node, const gfx::Vector2dF& delta); + // Resolves a delta in the given granularity for the |scroll_node| into + // physical pixels to scroll. + gfx::Vector2dF ResolveScrollGranularityToPixels( + const ScrollNode& scroll_node, + const gfx::Vector2dF& scroll_delta, + ui::ScrollGranularity granularity); + void ScheduleMicroBenchmark(std::unique_ptr<MicroBenchmarkImpl> benchmark); viz::CompositorFrameMetadata MakeCompositorFrameMetadata(); @@ -785,15 +806,6 @@ class CC_EXPORT LayerTreeHostImpl : public InputHandler, : task_runner_provider_->MainThreadTaskRunner(); } - // Determines whether the given scroll node can scroll on the compositor - // thread or if there are any reasons it must be scrolled on the main thread - // or not at all. Note: in general, this is not sufficient to determine if a - // scroll can occur on the compositor thread. If hit testing to a scroll - // node, the caller must also check whether the hit point intersects a - // non-fast-scrolling-region of any ancestor scrolling layers. - InputHandler::ScrollStatus TryScroll(const ScrollTree& scroll_tree, - ScrollNode* scroll_node) const; - // Return all ScrollNode indices that have an associated layer with a non-fast // region that intersects the point. base::flat_set<int> NonFastScrollableNodes( @@ -894,14 +906,20 @@ class CC_EXPORT LayerTreeHostImpl : public InputHandler, void CollectScrollDeltas(ScrollAndScaleSet* scroll_info); void CollectScrollbarUpdates(ScrollAndScaleSet* scroll_info) const; - gfx::Vector2dF ResolveScrollPercentageToPixels( - const ScrollNode& scroll_node, - const gfx::Vector2dF& resolved_pixels); - // Returns the ScrollNode we should use to scroll, accounting for viewport // scroll chaining rules. ScrollNode* GetNodeToScroll(ScrollNode* node) const; + // Determines whether the given scroll node can scroll on the compositor + // thread or if there are any reasons it must be scrolled on the main thread + // or not at all. Note: in general, this is not sufficient to determine if a + // scroll can occur on the compositor thread. If hit testing to a scroll + // node, the caller must also check whether the hit point intersects a + // non-fast-scrolling-region of any ancestor scrolling layers. Can be removed + // after scroll unification https://crbug.com/476553. + InputHandler::ScrollStatus TryScroll(const ScrollTree& scroll_tree, + ScrollNode* scroll_node) const; + // Transforms viewport start point and scroll delta to local start point and // local delta, respectively. If the transformation of either the start or end // point of a scroll is clipped, the function returns false. @@ -957,13 +975,14 @@ class CC_EXPORT LayerTreeHostImpl : public InputHandler, LayerImpl* first_scrolling_layer_or_drawn_scrollbar, ui::ScrollInputType type); - // Initial scroll hit testing can be unreliable in the presence of squashed - // layers. In this case, we fall back to main thread scrolling. This function - // compares |layer_impl| returned from a regular hit test to the layer - // returned from a hit test performed only on scrollers and scrollbars. If the - // closest scrolling ancestor of |layer_impl| is not the other layer, then the - // layer_impl must be a squasing layer overtop of some other scroller and we - // must rely on the main thread. + // |layer| is returned from a regular hit test, and + // |first_scrolling_layer_or_drawn_scrollbar| is returned from a hit test + // performed only on scrollers and scrollbars. Initial scroll hit testing can + // be unreliable if the latter is not the direct scroll ancestor of the + // former. In this case, we will fall back to main thread scrolling because + // the compositor thread doesn't know which layer to scroll. This happens when + // a layer covers a scroller that doesn't scroll the former, or a scroller is + // masked by a mask layer for mask image, clip-path, rounded border, etc. // // Note, position: fixed layers use the inner viewport as their ScrollNode // (since they don't scroll with the outer viewport), however, scrolls from @@ -1018,21 +1037,35 @@ class CC_EXPORT LayerTreeHostImpl : public InputHandler, void ClearCurrentlyScrollingNode(); // Performs a hit test to determine the ScrollNode to use when scrolling at - // |viewport_point|. Can return nullptr if the hit test fails; see the - // comment in IsInitialScrollHitTestReliable - ScrollNode* HitTestScrollNode(const gfx::PointF& device_viewport_point) const; + // |viewport_point|. If no layer is hit, this falls back to the inner + // viewport scroll node. Returns: + // - If |hit_test_sucessful| is false, hit testing has failed and the + // compositor cannot determine the correct scroll node (e.g. see comments in + // IsInitialScrollHitTestReliable). |scroll_node| is always nullptr in this + // case. + // - If |hit_test_successful| is true, returns the ScrollNode to use in + // |scroll_node|. This can be nullptr if no layer was hit and there are no + // viewport nodes (e.g. OOPIF, UI compositor). + struct ScrollHitTestResult { + ScrollNode* scroll_node; + bool hit_test_successful; + }; + ScrollHitTestResult HitTestScrollNode( + const gfx::PointF& device_viewport_point) const; // Similar to above but includes complicated logic to determine whether the // ScrollNode is able to be scrolled on the compositor or requires main // thread scrolling. If main thread scrolling is required // |scroll_on_main_thread| is set to true and the reason is given in // |main_thread_scrolling_reason| to on of the enum values in - // main_thread_scrolling_reason.h. + // main_thread_scrolling_reason.h. Can be removed after scroll unification + // https://crbug.com/476553. ScrollNode* FindScrollNodeForCompositedScrolling( const gfx::PointF& device_viewport_point, LayerImpl* layer_hit_by_point, bool* scroll_on_main_thread, uint32_t* main_thread_scrolling_reason) const; + void StartScrollbarFadeRecursive(LayerImpl* layer); void SetManagedMemoryPolicy(const ManagedMemoryPolicy& policy); @@ -1226,7 +1259,7 @@ class CC_EXPORT LayerTreeHostImpl : public InputHandler, std::unique_ptr<PageScaleAnimation> page_scale_animation_; - std::unique_ptr<FrameRateCounter> fps_counter_; + DroppedFrameCounter dropped_frame_counter_; std::unique_ptr<MemoryHistory> memory_history_; std::unique_ptr<DebugRectHistory> debug_rect_history_; @@ -1400,6 +1433,7 @@ class CC_EXPORT LayerTreeHostImpl : public InputHandler, std::unique_ptr<LCDTextMetricsReporter> lcd_text_metrics_reporter_; FrameRateEstimator frame_rate_estimator_; + bool has_observed_first_scroll_delay_ = false; // Must be the last member to ensure this is destroyed first in the // destruction order and invalidates all weak pointers. diff --git a/chromium/cc/trees/layer_tree_host_impl_unittest.cc b/chromium/cc/trees/layer_tree_host_impl_unittest.cc index 3d1cfc76a5d..fd82323bf7e 100644 --- a/chromium/cc/trees/layer_tree_host_impl_unittest.cc +++ b/chromium/cc/trees/layer_tree_host_impl_unittest.cc @@ -27,10 +27,12 @@ #include "cc/animation/animation_host.h" #include "cc/animation/animation_id_provider.h" #include "cc/animation/transform_operations.h" +#include "cc/base/features.h" #include "cc/base/histograms.h" #include "cc/input/browser_controls_offset_manager.h" #include "cc/input/main_thread_scrolling_reason.h" #include "cc/input/page_scale_animation.h" +#include "cc/input/scroll_utils.h" #include "cc/layers/append_quads_data.h" #include "cc/layers/heads_up_display_layer_impl.h" #include "cc/layers/layer_impl.h" @@ -262,7 +264,16 @@ class LayerTreeHostImplTest : public testing::Test, void NotifyPaintWorkletStateChange( Scheduler::PaintWorkletState state) override {} void NotifyThroughputTrackerResults(CustomTrackerResults results) override {} + void SubmitThroughputData(ukm::SourceId source_id, + int aggregated_percent, + int impl_percent, + base::Optional<int> main_percent) override {} + void DidObserveFirstScrollDelay( + base::TimeDelta first_scroll_delay, + base::TimeTicks first_scroll_timestamp) override { + first_scroll_observed++; + } void set_reduce_memory_result(bool reduce_memory_result) { reduce_memory_result_ = reduce_memory_result; } @@ -496,15 +507,21 @@ class LayerTreeHostImplTest : public testing::Test, layer_tree_impl->DidBecomeActive(); // The point hits squash1 layer and also scroll layer, because scroll layer - // is not an ancestor of squash1 layer, we cannot scroll on impl thread. + // is not an ancestor of squash1 layer, we cannot scroll on impl thread + // (without at least a hit test on the main thread). InputHandler::ScrollStatus status = host_impl_->ScrollBegin( BeginState(gfx::Point(230, 150), gfx::Vector2dF(0, 10), ui::ScrollInputType::kWheel) .get(), ui::ScrollInputType::kWheel); - ASSERT_EQ(InputHandler::SCROLL_UNKNOWN, status.thread); - ASSERT_EQ(MainThreadScrollingReason::kFailedHitTest, - status.main_thread_scrolling_reasons); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + ASSERT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + ASSERT_TRUE(status.needs_main_thread_hit_test); + } else { + ASSERT_EQ(InputHandler::SCROLL_UNKNOWN, status.thread); + ASSERT_EQ(MainThreadScrollingReason::kFailedHitTest, + status.main_thread_scrolling_reasons); + } // The point hits squash1 layer and also scrollbar layer. status = host_impl_->ScrollBegin( @@ -512,9 +529,14 @@ class LayerTreeHostImplTest : public testing::Test, ui::ScrollInputType::kWheel) .get(), ui::ScrollInputType::kWheel); - ASSERT_EQ(InputHandler::SCROLL_UNKNOWN, status.thread); - ASSERT_EQ(MainThreadScrollingReason::kFailedHitTest, - status.main_thread_scrolling_reasons); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + ASSERT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + ASSERT_TRUE(status.needs_main_thread_hit_test); + } else { + ASSERT_EQ(InputHandler::SCROLL_UNKNOWN, status.thread); + ASSERT_EQ(MainThreadScrollingReason::kFailedHitTest, + status.main_thread_scrolling_reasons); + } // The point hits squash2 layer and also scroll layer, because scroll layer // is an ancestor of squash2 layer, we should scroll on impl. @@ -670,8 +692,8 @@ class LayerTreeHostImplTest : public testing::Test, host_impl_ = nullptr; } - void WhiteListedTouchActionTestHelper(float device_scale_factor, - float page_scale_factor) { + void AllowedTouchActionTestHelper(float device_scale_factor, + float page_scale_factor) { SetupViewportLayersInnerScrolls(gfx::Size(100, 100), gfx::Size(200, 200)); DrawFrame(); @@ -825,6 +847,26 @@ class LayerTreeHostImplTest : public testing::Test, scoped_refptr<AnimationTimeline> timeline_; std::unique_ptr<base::Thread> image_worker_; int next_layer_id_ = 2; + int first_scroll_observed = 0; +}; + +// Allows parameterizing the above class on the scroll unification flag. We +// can't parameterize the base class itself because it serves as a base to +// other parameterized descendants. Can be removed when unification ships. +class ScrollUnifiedLayerTreeHostImplTest + : public LayerTreeHostImplTest, + public testing::WithParamInterface<bool> { + public: + ScrollUnifiedLayerTreeHostImplTest() { + if (GetParam()) { + scoped_feature_list.InitAndEnableFeature(features::kScrollUnification); + } else { + scoped_feature_list.InitAndDisableFeature(features::kScrollUnification); + } + } + + private: + base::test::ScopedFeatureList scoped_feature_list; }; class CommitToPendingTreeLayerTreeHostImplTest : public LayerTreeHostImplTest { @@ -901,7 +943,11 @@ class TestInputHandlerClient : public InputHandlerClient { float max_page_scale_factor_; }; -TEST_F(LayerTreeHostImplTest, LocalAndExternalPinchState) { +INSTANTIATE_TEST_SUITE_P(All, + ScrollUnifiedLayerTreeHostImplTest, + testing::Bool()); + +TEST_P(ScrollUnifiedLayerTreeHostImplTest, LocalAndExternalPinchState) { // PinchGestureBegin/End update pinch_gesture_active() properly. EXPECT_FALSE(host_impl_->pinch_gesture_active()); host_impl_->PinchGestureBegin(); @@ -925,7 +971,7 @@ TEST_F(LayerTreeHostImplTest, LocalAndExternalPinchState) { EXPECT_FALSE(host_impl_->pinch_gesture_active()); } -TEST_F(LayerTreeHostImplTest, NotifyIfCanDrawChanged) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, NotifyIfCanDrawChanged) { // Note: It is not possible to disable the renderer once it has been set, // so we do not need to test that disabling the renderer notifies us // that can_draw changed. @@ -961,7 +1007,7 @@ TEST_F(LayerTreeHostImplTest, NotifyIfCanDrawChanged) { on_can_draw_state_changed_called_ = false; } -TEST_F(LayerTreeHostImplTest, ResourcelessDrawWithEmptyViewport) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ResourcelessDrawWithEmptyViewport) { CreateHostImpl(DefaultSettings(), FakeLayerTreeFrameSink::CreateSoftware()); SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); @@ -979,7 +1025,7 @@ TEST_F(LayerTreeHostImplTest, ResourcelessDrawWithEmptyViewport) { ASSERT_EQ(fake_layer_tree_frame_sink->num_sent_frames(), 1u); } -TEST_F(LayerTreeHostImplTest, ScrollDeltaNoLayers) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollDeltaNoLayers) { host_impl_->active_tree()->SetRootLayerForTesting(nullptr); std::unique_ptr<ScrollAndScaleSet> scroll_info = @@ -987,7 +1033,7 @@ TEST_F(LayerTreeHostImplTest, ScrollDeltaNoLayers) { ASSERT_EQ(scroll_info->scrolls.size(), 0u); } -TEST_F(LayerTreeHostImplTest, ScrollDeltaTreeButNoChanges) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollDeltaTreeButNoChanges) { LayerImpl* root = SetupDefaultRootLayer(gfx::Size(10, 10)); { LayerImpl* child1 = AddLayer(); @@ -1015,7 +1061,7 @@ TEST_F(LayerTreeHostImplTest, ScrollDeltaTreeButNoChanges) { ExpectClearedScrollDeltasRecursive(root); } -TEST_F(LayerTreeHostImplTest, ScrollDeltaRepeatedScrolls) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollDeltaRepeatedScrolls) { gfx::ScrollOffset scroll_offset(20, 30); gfx::ScrollOffset scroll_delta(11, -15); @@ -1075,7 +1121,9 @@ TEST_F(CommitToPendingTreeLayerTreeHostImplTest, "Compositing.Renderer.GPUMemoryForTilingsInKb", 1); } -TEST_F(LayerTreeHostImplTest, ScrollBeforeRootLayerAttached) { +// This test verifies that we drop a scroll (and don't crash) if a scroll is +// received before the root layer has been attached. https://crbug.com/895817. +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollBeforeRootLayerAttached) { InputHandler::ScrollStatus status = host_impl_->ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, 1), ui::ScrollInputType::kWheel) @@ -1100,7 +1148,7 @@ TEST_F(LayerTreeHostImplTest, ScrollBeforeRootLayerAttached) { // to pre-commit input deferral which causes some input events to be dropped // before the first commit in a renderer has occurred. See the flag // kAllowPreCommitInput and how it's used. -TEST_F(LayerTreeHostImplTest, ScrollUpdateAndEndNoOpWithoutBegin) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollUpdateAndEndNoOpWithoutBegin) { SetupViewportLayersOuterScrolls(gfx::Size(100, 100), gfx::Size(1000, 1000)); // Simulate receiving a gesture mid-stream so that the Begin wasn't ever @@ -1141,7 +1189,7 @@ TEST_F(LayerTreeHostImplTest, ScrollUpdateAndEndNoOpWithoutBegin) { // Test that specifying a scroller to ScrollBegin (i.e. avoid hit testing) // returns the correct status if the scroller cannot be scrolled on the // compositor thread. -TEST_F(LayerTreeHostImplTest, TargetMainThreadScroller) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, TargetMainThreadScroller) { SetupViewportLayersOuterScrolls(gfx::Size(100, 100), gfx::Size(1000, 1000)); ScrollStateData scroll_state_data; @@ -1160,22 +1208,32 @@ TEST_F(LayerTreeHostImplTest, TargetMainThreadScroller) { } // Now add a MainThreadScrollingReason. Trying to target the node this time - // should result in a failed ScrollBegin with the appropriate return value. + // should result in a failed ScrollBegin with the appropriate return value, + // unless scroll unification is enabled - since all nodes can be scrolled + // from the compositor in that mode. host_impl_->OuterViewportScrollNode()->main_thread_scrolling_reasons = MainThreadScrollingReason::kThreadedScrollingDisabled; { InputHandler::ScrollStatus status = host_impl_->ScrollBegin( scroll_state.get(), ui::ScrollInputType::kWheel); - EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); - EXPECT_EQ( - host_impl_->OuterViewportScrollNode()->main_thread_scrolling_reasons, - status.main_thread_scrolling_reasons); - EXPECT_FALSE(host_impl_->CurrentlyScrollingNode()); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_FALSE(status.needs_main_thread_hit_test); + EXPECT_EQ( + MainThreadScrollingReason::kThreadedScrollingDisabled, + host_impl_->CurrentlyScrollingNode()->main_thread_scrolling_reasons); + } else { + EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); + EXPECT_EQ( + host_impl_->OuterViewportScrollNode()->main_thread_scrolling_reasons, + status.main_thread_scrolling_reasons); + EXPECT_FALSE(host_impl_->CurrentlyScrollingNode()); + } } } -TEST_F(LayerTreeHostImplTest, ScrollRootCallsCommitAndRedraw) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollRootCallsCommitAndRedraw) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); DrawFrame(); @@ -1206,7 +1264,8 @@ TEST_F(LayerTreeHostImplTest, ScrollRootCallsCommitAndRedraw) { // makes some sense since touchscreen/high-precision touchpad scrolling has a // physical metaphor (movement sticks to finger) so smoothness should be // prioritized. -TEST_F(LayerTreeHostImplTest, ActivelyTouchScrollingOnlyAfterScrollMovement) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ActivelyTouchScrollingOnlyAfterScrollMovement) { SetupViewportLayersOuterScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); DrawFrame(); @@ -1287,7 +1346,7 @@ TEST_F(LayerTreeHostImplTest, ActivelyTouchScrollingOnlyAfterScrollMovement) { } } -TEST_F(LayerTreeHostImplTest, ScrollWithoutRootLayer) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollWithoutRootLayer) { // We should not crash when trying to scroll an empty layer tree. InputHandler::ScrollStatus status = host_impl_->ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, 10), @@ -1299,7 +1358,7 @@ TEST_F(LayerTreeHostImplTest, ScrollWithoutRootLayer) { status.main_thread_scrolling_reasons); } -TEST_F(LayerTreeHostImplTest, ScrollWithoutRenderer) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollWithoutRenderer) { auto gl_owned = std::make_unique<viz::TestGLES2Interface>(); gl_owned->LoseContextCHROMIUM(GL_GUILTY_CONTEXT_RESET_ARB, GL_INNOCENT_CONTEXT_RESET_ARB); @@ -1323,7 +1382,7 @@ TEST_F(LayerTreeHostImplTest, ScrollWithoutRenderer) { status.main_thread_scrolling_reasons); } -TEST_F(LayerTreeHostImplTest, ReplaceTreeWhileScrolling) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ReplaceTreeWhileScrolling) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); DrawFrame(); @@ -1356,7 +1415,8 @@ TEST_F(LayerTreeHostImplTest, ReplaceTreeWhileScrolling) { scroll_delta)); } -TEST_F(LayerTreeHostImplTest, ActivateTreeScrollingNodeDisappeared) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ActivateTreeScrollingNodeDisappeared) { SetupViewportLayersOuterScrolls(gfx::Size(100, 100), gfx::Size(1000, 1000)); auto status = host_impl_->ScrollBegin( @@ -1385,7 +1445,7 @@ TEST_F(LayerTreeHostImplTest, ActivateTreeScrollingNodeDisappeared) { EXPECT_FALSE(host_impl_->active_tree()->CurrentlyScrollingNode()); } -TEST_F(LayerTreeHostImplTest, ScrollBlocksOnWheelEventHandlers) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollBlocksOnWheelEventHandlers) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); auto* scroll = InnerViewportScrollLayer(); scroll->SetWheelEventHandlerRegion(Region(gfx::Rect(20, 20))); @@ -1415,7 +1475,7 @@ TEST_F(LayerTreeHostImplTest, ScrollBlocksOnWheelEventHandlers) { host_impl_->ScrollEnd(); } -TEST_F(LayerTreeHostImplTest, ScrollBlocksOnTouchEventHandlers) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollBlocksOnTouchEventHandlers) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); DrawFrame(); @@ -1467,9 +1527,9 @@ TEST_F(LayerTreeHostImplTest, ScrollBlocksOnTouchEventHandlers) { EXPECT_EQ(TouchAction::kPanX, touch_action); } -TEST_F(LayerTreeHostImplTest, ShouldScrollOnMainThread) { - SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); - host_impl_->InnerViewportScrollNode()->main_thread_scrolling_reasons = +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ShouldScrollOnMainThread) { + SetupViewportLayersOuterScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); + host_impl_->OuterViewportScrollNode()->main_thread_scrolling_reasons = MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects; DrawFrame(); @@ -1478,30 +1538,48 @@ TEST_F(LayerTreeHostImplTest, ShouldScrollOnMainThread) { ui::ScrollInputType::kWheel) .get(), ui::ScrollInputType::kWheel); - EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); - EXPECT_EQ(MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects, - status.main_thread_scrolling_reasons); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_FALSE(status.needs_main_thread_hit_test); + EXPECT_EQ( + MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects, + host_impl_->CurrentlyScrollingNode()->main_thread_scrolling_reasons); + } else { + EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects, + status.main_thread_scrolling_reasons); + } status = host_impl_->ScrollBegin(BeginState(gfx::Point(), gfx::Vector2d(0, 10), ui::ScrollInputType::kTouchscreen) .get(), ui::ScrollInputType::kTouchscreen); - EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); - EXPECT_EQ(MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects, - status.main_thread_scrolling_reasons); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_FALSE(status.needs_main_thread_hit_test); + EXPECT_EQ( + MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects, + host_impl_->CurrentlyScrollingNode()->main_thread_scrolling_reasons); + } else { + EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects, + status.main_thread_scrolling_reasons); + } } -TEST_F(LayerTreeHostImplTest, ScrollWithOverlappingNonScrollableLayer) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ScrollWithOverlappingNonScrollableLayer) { CreateAndTestNonScrollableLayers(false); } -TEST_F(LayerTreeHostImplTest, +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollWithOverlappingTransparentNonScrollableLayer) { CreateAndTestNonScrollableLayers(true); } -TEST_F(LayerTreeHostImplTest, ScrolledOverlappingDrawnScrollbarLayer) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ScrolledOverlappingDrawnScrollbarLayer) { LayerTreeImpl* layer_tree_impl = host_impl_->active_tree(); gfx::Size content_size = gfx::Size(360, 600); gfx::Size scroll_content_size = gfx::Size(345, 3800); @@ -1534,9 +1612,16 @@ TEST_F(LayerTreeHostImplTest, ScrolledOverlappingDrawnScrollbarLayer) { ui::ScrollInputType::kWheel) .get(), ui::ScrollInputType::kWheel); - EXPECT_EQ(InputHandler::SCROLL_UNKNOWN, status.thread); - EXPECT_EQ(MainThreadScrollingReason::kFailedHitTest, - status.main_thread_scrolling_reasons); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain, + status.main_thread_scrolling_reasons); + EXPECT_TRUE(status.needs_main_thread_hit_test); + } else { + EXPECT_EQ(InputHandler::SCROLL_UNKNOWN, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kFailedHitTest, + status.main_thread_scrolling_reasons); + } // The point hits the drawn scrollbar layer completely and should scroll on // the impl thread. @@ -1623,7 +1708,7 @@ TEST_F(LayerTreeHostImplTestInvokeMainThreadCallbacks, mock_details.presentation_feedback); } -TEST_F(LayerTreeHostImplTest, NonFastScrollableRegionBasic) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, NonFastScrollableRegionBasic) { SetupViewportLayersInnerScrolls(gfx::Size(100, 100), gfx::Size(200, 200)); LayerImpl* outer_scroll = OuterViewportScrollLayer(); @@ -1631,24 +1716,40 @@ TEST_F(LayerTreeHostImplTest, NonFastScrollableRegionBasic) { DrawFrame(); - // All scroll types inside the non-fast scrollable region should fail. + // All scroll types inside the non-fast scrollable region should fail. When + // scroll unification is enabled, these scrolls succeed but request a main + // thread hit test. InputHandler::ScrollStatus status = host_impl_->ScrollBegin( BeginState(gfx::Point(25, 25), gfx::Vector2d(0, 10), ui::ScrollInputType::kWheel) .get(), ui::ScrollInputType::kWheel); - EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); - EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion, - status.main_thread_scrolling_reasons); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain, + status.main_thread_scrolling_reasons); + EXPECT_TRUE(status.needs_main_thread_hit_test); + } else { + EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion, + status.main_thread_scrolling_reasons); + } status = host_impl_->ScrollBegin( BeginState(gfx::Point(25, 25), gfx::Vector2d(0, 10), ui::ScrollInputType::kTouchscreen) .get(), ui::ScrollInputType::kTouchscreen); - EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); - EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion, - status.main_thread_scrolling_reasons); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain, + status.main_thread_scrolling_reasons); + EXPECT_TRUE(status.needs_main_thread_hit_test); + } else { + EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion, + status.main_thread_scrolling_reasons); + } // All scroll types outside this region should succeed. status = host_impl_->ScrollBegin( @@ -1679,7 +1780,7 @@ TEST_F(LayerTreeHostImplTest, NonFastScrollableRegionBasic) { host_impl_->ScrollEnd(); } -TEST_F(LayerTreeHostImplTest, NonFastScrollableRegionWithOffset) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, NonFastScrollableRegionWithOffset) { SetupViewportLayersInnerScrolls(gfx::Size(100, 100), gfx::Size(200, 200)); LayerImpl* outer_scroll = OuterViewportScrollLayer(); @@ -1699,6 +1800,7 @@ TEST_F(LayerTreeHostImplTest, NonFastScrollableRegionWithOffset) { EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain, status.main_thread_scrolling_reasons); + EXPECT_FALSE(status.needs_main_thread_hit_test); host_impl_->ScrollUpdate(UpdateState(gfx::Point(), gfx::Vector2d(0, 1), ui::ScrollInputType::kWheel) @@ -1711,12 +1813,19 @@ TEST_F(LayerTreeHostImplTest, NonFastScrollableRegionWithOffset) { ui::ScrollInputType::kWheel) .get(), ui::ScrollInputType::kWheel); - EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); - EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion, - status.main_thread_scrolling_reasons); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain, + status.main_thread_scrolling_reasons); + EXPECT_TRUE(status.needs_main_thread_hit_test); + } else { + EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion, + status.main_thread_scrolling_reasons); + } } -TEST_F(LayerTreeHostImplTest, ScrollHandlerNotPresent) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollHandlerNotPresent) { SetupViewportLayersInnerScrolls(gfx::Size(100, 100), gfx::Size(200, 200)); EXPECT_FALSE(host_impl_->active_tree()->have_scroll_event_handlers()); DrawFrame(); @@ -1731,7 +1840,7 @@ TEST_F(LayerTreeHostImplTest, ScrollHandlerNotPresent) { EXPECT_FALSE(host_impl_->scroll_affects_scroll_handler()); } -TEST_F(LayerTreeHostImplTest, ScrollHandlerPresent) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollHandlerPresent) { SetupViewportLayersInnerScrolls(gfx::Size(100, 100), gfx::Size(200, 200)); host_impl_->active_tree()->set_have_scroll_event_handlers(true); DrawFrame(); @@ -1746,7 +1855,7 @@ TEST_F(LayerTreeHostImplTest, ScrollHandlerPresent) { EXPECT_FALSE(host_impl_->scroll_affects_scroll_handler()); } -TEST_F(LayerTreeHostImplTest, ScrollUpdateReturnsCorrectValue) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollUpdateReturnsCorrectValue) { SetupViewportLayersInnerScrolls(gfx::Size(100, 100), gfx::Size(200, 200)); DrawFrame(); @@ -1848,7 +1957,7 @@ TEST_F(LayerTreeHostImplTest, ScrollUpdateReturnsCorrectValue) { // TODO(sunyunjia): Move scroll snap tests to a separate file. // https://crbug.com/851690 -TEST_F(LayerTreeHostImplTest, ScrollSnapOnX) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollSnapOnX) { LayerImpl* overflow = CreateLayerForSnapping(); gfx::Point pointer_position(10, 10); @@ -1893,7 +2002,7 @@ TEST_F(LayerTreeHostImplTest, ScrollSnapOnX) { GetSnapContainerData(overflow)->GetTargetSnapAreaElementIds()); } -TEST_F(LayerTreeHostImplTest, ScrollSnapOnY) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollSnapOnY) { LayerImpl* overflow = CreateLayerForSnapping(); gfx::Point pointer_position(10, 10); @@ -1938,7 +2047,7 @@ TEST_F(LayerTreeHostImplTest, ScrollSnapOnY) { GetSnapContainerData(overflow)->GetTargetSnapAreaElementIds()); } -TEST_F(LayerTreeHostImplTest, ScrollSnapOnBoth) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollSnapOnBoth) { LayerImpl* overflow = CreateLayerForSnapping(); gfx::Point pointer_position(10, 10); @@ -1984,7 +2093,7 @@ TEST_F(LayerTreeHostImplTest, ScrollSnapOnBoth) { // Simulate a ScrollBegin and ScrollEnd without any intervening ScrollUpdate. // This test passes if it doesn't crash. -TEST_F(LayerTreeHostImplTest, SnapAfterEmptyScroll) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, SnapAfterEmptyScroll) { CreateLayerForSnapping(); gfx::Point pointer_position(10, 10); @@ -1999,7 +2108,7 @@ TEST_F(LayerTreeHostImplTest, SnapAfterEmptyScroll) { host_impl_->ScrollEnd(true); } -TEST_F(LayerTreeHostImplTest, ScrollSnapAfterAnimatedScroll) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollSnapAfterAnimatedScroll) { LayerImpl* overflow = CreateLayerForSnapping(); gfx::Point pointer_position(10, 10); @@ -2057,7 +2166,7 @@ TEST_F(LayerTreeHostImplTest, ScrollSnapAfterAnimatedScroll) { GetSnapContainerData(overflow)->GetTargetSnapAreaElementIds()); } -TEST_F(LayerTreeHostImplTest, SnapAnimationTargetUpdated) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, SnapAnimationTargetUpdated) { LayerImpl* overflow = CreateLayerForSnapping(); gfx::Point pointer_position(10, 10); @@ -2120,7 +2229,7 @@ TEST_F(LayerTreeHostImplTest, SnapAnimationTargetUpdated) { EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 50), CurrentScrollOffset(overflow)); } -TEST_F(LayerTreeHostImplTest, SnapAnimationCancelledByScroll) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, SnapAnimationCancelledByScroll) { LayerImpl* overflow = CreateLayerForSnapping(); gfx::Point pointer_position(10, 10); @@ -2183,7 +2292,7 @@ TEST_F(LayerTreeHostImplTest, SnapAnimationCancelledByScroll) { GetSnapContainerData(overflow)->GetTargetSnapAreaElementIds()); } -TEST_F(LayerTreeHostImplTest, +TEST_P(ScrollUnifiedLayerTreeHostImplTest, SnapAnimationShouldNotStartWhenScrollEndsAtSnapTarget) { LayerImpl* overflow = CreateLayerForSnapping(); @@ -2230,7 +2339,7 @@ TEST_F(LayerTreeHostImplTest, GetSnapContainerData(overflow)->GetTargetSnapAreaElementIds()); } -TEST_F(LayerTreeHostImplTest, +TEST_P(ScrollUnifiedLayerTreeHostImplTest, GetSnapFlingInfoAndSetAnimatingSnapTargetWhenZoomed) { LayerImpl* overflow = CreateLayerForSnapping(); // Scales the page to its 1/5. @@ -2270,7 +2379,8 @@ TEST_F(LayerTreeHostImplTest, GetSnapContainerData(overflow)->GetTargetSnapAreaElementIds()); } -TEST_F(LayerTreeHostImplTest, SnapFlingAnimationEndWithoutFinishing) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + SnapFlingAnimationEndWithoutFinishing) { LayerImpl* overflow = CreateLayerForSnapping(); // Scales the page to its 1/5. host_impl_->active_tree()->PushPageScaleFromMainThread(0.2f, 0.1f, 5.f); @@ -2310,7 +2420,8 @@ TEST_F(LayerTreeHostImplTest, SnapFlingAnimationEndWithoutFinishing) { GetSnapContainerData(overflow)->GetTargetSnapAreaElementIds()); } -TEST_F(LayerTreeHostImplTest, OverscrollBehaviorPreventsPropagation) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + OverscrollBehaviorPreventsPropagation) { const gfx::Size kViewportSize(100, 100); const gfx::Size kContentSize(200, 200); SetupViewportLayersOuterScrolls(kViewportSize, kContentSize); @@ -2508,7 +2619,7 @@ TEST_F(LayerTreeHostImplTest, OverscrollBehaviorPreventsPropagation) { EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 10), CurrentScrollOffset(overflow)); } -TEST_F(LayerTreeHostImplTest, ScrollWithUserUnscrollableLayers) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollWithUserUnscrollableLayers) { const gfx::Size kViewportSize(100, 100); const gfx::Size kContentSize(200, 200); SetupViewportLayersOuterScrolls(kViewportSize, kContentSize); @@ -2582,7 +2693,10 @@ TEST_F(LayerTreeHostImplTest, ScrollWithUserUnscrollableLayers) { EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 20), CurrentScrollOffset(overflow)); } -TEST_F(LayerTreeHostImplTest, ForceMainThreadScrollWithoutScrollLayer) { +// Test that if a scroll node doesn't have an associated Layer, scrolling +// is forced to the main thread. With scroll unification, this kind of scroll +// is now handled on the impl thread but will force a repaint on each scroll. +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollNodeWithoutScrollLayer) { SetupViewportLayersInnerScrolls(gfx::Size(100, 100), gfx::Size(200, 200)); ScrollNode* scroll_node = host_impl_->OuterViewportScrollNode(); // Change the scroll node so that it no longer has an associated layer. @@ -2595,9 +2709,20 @@ TEST_F(LayerTreeHostImplTest, ForceMainThreadScrollWithoutScrollLayer) { ui::ScrollInputType::kWheel) .get(), ui::ScrollInputType::kWheel); - EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); - EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion, - status.main_thread_scrolling_reasons); + + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain, + status.main_thread_scrolling_reasons); + // We don't have a layer for the scroller but we didn't hit a non-fast + // scrolling region or fail hit testing the layer - we don't need a main + // thread hit test in this case. + EXPECT_FALSE(status.needs_main_thread_hit_test); + } else { + EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion, + status.main_thread_scrolling_reasons); + } } TEST_F(CommitToPendingTreeLayerTreeHostImplTest, @@ -2712,7 +2837,8 @@ TEST_F(CommitToPendingTreeLayerTreeHostImplTest, EXPECT_FALSE(did_request_commit_); } -TEST_F(LayerTreeHostImplTest, AnimationSchedulingCommitToActiveTree) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + AnimationSchedulingCommitToActiveTree) { EXPECT_TRUE(host_impl_->CommitToActiveTree()); auto* root = SetupDefaultRootLayer(gfx::Size(50, 50)); @@ -2749,7 +2875,8 @@ TEST_F(LayerTreeHostImplTest, AnimationSchedulingCommitToActiveTree) { host_impl_ = nullptr; } -TEST_F(LayerTreeHostImplTest, AnimationSchedulingOnLayerDestruction) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + AnimationSchedulingOnLayerDestruction) { LayerImpl* root = SetupDefaultRootLayer(gfx::Size(50, 50)); LayerImpl* child = AddLayer(); @@ -2828,7 +2955,7 @@ class MissingTilesLayer : public LayerImpl { bool has_missing_tiles_; }; -TEST_F(LayerTreeHostImplTest, ImplPinchZoom) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ImplPinchZoom) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); DrawFrame(); @@ -2909,7 +3036,7 @@ TEST_F(LayerTreeHostImplTest, ImplPinchZoom) { } } -TEST_F(LayerTreeHostImplTest, ViewportScrollbarGeometry) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ViewportScrollbarGeometry) { // Tests for correct behavior of solid color scrollbars on unscrollable pages // under tricky fractional scale/size issues. @@ -2966,7 +3093,7 @@ TEST_F(LayerTreeHostImplTest, ViewportScrollbarGeometry) { EXPECT_TRUE(h_scrollbar->CanScrollOrientation()); } -TEST_F(LayerTreeHostImplTest, ViewportScrollOrder) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ViewportScrollOrder) { LayerTreeSettings settings = DefaultSettings(); CreateHostImpl(settings, CreateLayerTreeFrameSink()); host_impl_->active_tree()->PushPageScaleFromMainThread(1, 0.25f, 4); @@ -3036,7 +3163,8 @@ TEST_F(LayerTreeHostImplTest, ViewportScrollOrder) { // Make sure scrolls smaller than a unit applied to the viewport don't get // dropped. crbug.com/539334. -TEST_F(LayerTreeHostImplTest, ScrollViewportWithFractionalAmounts) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ScrollViewportWithFractionalAmounts) { host_impl_->active_tree()->PushPageScaleFromMainThread(1, 1, 2); const gfx::Size content_size(1000, 1000); @@ -3093,7 +3221,7 @@ TEST_F(LayerTreeHostImplTest, ScrollViewportWithFractionalAmounts) { // Tests that scrolls during a pinch gesture (i.e. "two-finger" scrolls) work // as expected. That is, scrolling during a pinch should bubble from the inner // to the outer viewport. -TEST_F(LayerTreeHostImplTest, ScrollDuringPinchGesture) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollDuringPinchGesture) { LayerTreeSettings settings = DefaultSettings(); CreateHostImpl(settings, CreateLayerTreeFrameSink()); host_impl_->active_tree()->PushPageScaleFromMainThread(1, 1, 2); @@ -3153,7 +3281,7 @@ TEST_F(LayerTreeHostImplTest, ScrollDuringPinchGesture) { // Tests the "snapping" of pinch-zoom gestures to the screen edge. That is, when // a pinch zoom is anchored within a certain margin of the screen edge, we // should assume the user means to scroll into the edge of the screen. -TEST_F(LayerTreeHostImplTest, PinchZoomSnapsToScreenEdge) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, PinchZoomSnapsToScreenEdge) { LayerTreeSettings settings = DefaultSettings(); CreateHostImpl(settings, CreateLayerTreeFrameSink()); host_impl_->active_tree()->PushPageScaleFromMainThread(1, 1, 2); @@ -3242,7 +3370,8 @@ TEST_F(LayerTreeHostImplTest, PinchZoomSnapsToScreenEdge) { CurrentScrollOffset(InnerViewportScrollLayer())); } -TEST_F(LayerTreeHostImplTest, ImplPinchZoomWheelBubbleBetweenViewports) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ImplPinchZoomWheelBubbleBetweenViewports) { const gfx::Size content_size(200, 200); const gfx::Size viewport_size(100, 100); SetupViewportLayersOuterScrolls(viewport_size, content_size); @@ -3307,7 +3436,7 @@ TEST_F(LayerTreeHostImplTest, ImplPinchZoomWheelBubbleBetweenViewports) { CurrentScrollOffset(inner_scroll_layer)); } -TEST_F(LayerTreeHostImplTest, ScrollWithSwapPromises) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollWithSwapPromises) { ui::LatencyInfo latency_info; latency_info.set_trace_id(5); latency_info.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT); @@ -3337,7 +3466,7 @@ TEST_F(LayerTreeHostImplTest, ScrollWithSwapPromises) { // Test that scrolls targeting a layer with a non-null scroll_parent() don't // bubble up. -TEST_F(LayerTreeHostImplTest, ScrollDoesntBubble) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollDoesntBubble) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); LayerImpl* viewport_scroll = InnerViewportScrollLayer(); @@ -3420,7 +3549,7 @@ TEST_F(LayerTreeHostImplTest, ScrollDoesntBubble) { } } -TEST_F(LayerTreeHostImplTest, PinchGesture) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, PinchGesture) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); DrawFrame(); @@ -3610,7 +3739,7 @@ TEST_F(LayerTreeHostImplTest, PinchGesture) { } } -TEST_F(LayerTreeHostImplTest, SyncSubpixelScrollDelta) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, SyncSubpixelScrollDelta) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); DrawFrame(); @@ -3660,7 +3789,8 @@ TEST_F(LayerTreeHostImplTest, SyncSubpixelScrollDelta) { scroll_layer->ScreenSpaceTransform().To2dTranslation()); } -TEST_F(LayerTreeHostImplTest, SyncSubpixelScrollFromFractionalActiveBase) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + SyncSubpixelScrollFromFractionalActiveBase) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); DrawFrame(); @@ -3699,7 +3829,8 @@ TEST_F(LayerTreeHostImplTest, SyncSubpixelScrollFromFractionalActiveBase) { gfx::Vector2dF(0, -1)); } -TEST_F(LayerTreeHostImplTest, PinchZoomTriggersPageScaleAnimation) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + PinchZoomTriggersPageScaleAnimation) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); DrawFrame(); @@ -3821,7 +3952,7 @@ TEST_F(LayerTreeHostImplTest, PinchZoomTriggersPageScaleAnimation) { } } -TEST_F(LayerTreeHostImplTest, PageScaleAnimation) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, PageScaleAnimation) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); DrawFrame(); @@ -3950,7 +4081,7 @@ TEST_F(LayerTreeHostImplTest, PageScaleAnimation) { } } -TEST_F(LayerTreeHostImplTest, PageScaleAnimationNoOp) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, PageScaleAnimationNoOp) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); DrawFrame(); @@ -4008,7 +4139,8 @@ TEST_F(LayerTreeHostImplTest, PageScaleAnimationNoOp) { } } -TEST_F(LayerTreeHostImplTest, PageScaleAnimationTransferedOnSyncTreeActivate) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + PageScaleAnimationTransferedOnSyncTreeActivate) { CreatePendingTree(); host_impl_->pending_tree()->PushPageScaleFromMainThread(1, 1, 1); SetupViewportLayers(host_impl_->pending_tree(), gfx::Size(50, 50), @@ -4132,7 +4264,8 @@ TEST_F(LayerTreeHostImplTest, PageScaleAnimationTransferedOnSyncTreeActivate) { gfx::ScrollOffset(-50, -50))); } -TEST_F(LayerTreeHostImplTest, PageScaleAnimationCompletedNotification) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + PageScaleAnimationCompletedNotification) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); DrawFrame(); @@ -4181,7 +4314,8 @@ TEST_F(LayerTreeHostImplTest, PageScaleAnimationCompletedNotification) { host_impl_->DidFinishImplFrame(begin_frame_args); } -TEST_F(LayerTreeHostImplTest, MaxScrollOffsetAffectedByViewportBoundsDelta) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + MaxScrollOffsetAffectedByViewportBoundsDelta) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); host_impl_->active_tree()->PushPageScaleFromMainThread(1, 0.5f, 4); DrawFrame(); @@ -4207,7 +4341,8 @@ TEST_F(LayerTreeHostImplTest, MaxScrollOffsetAffectedByViewportBoundsDelta) { // Ensures scroll gestures coming from scrollbars cause animations in the // appropriate scenarios. -TEST_F(LayerTreeHostImplTest, AnimatedGranularityCausesSmoothScroll) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + AnimatedGranularityCausesSmoothScroll) { gfx::Size viewport_size(300, 200); gfx::Size content_size(1000, 1000); SetupViewportLayersOuterScrolls(viewport_size, content_size); @@ -4275,7 +4410,8 @@ TEST_F(LayerTreeHostImplTest, AnimatedGranularityCausesSmoothScroll) { // Ensures scroll gestures coming from scrollbars don't cause animations if // smooth scrolling is disabled. -TEST_F(LayerTreeHostImplTest, NonAnimatedGranularityCausesInstantScroll) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + NonAnimatedGranularityCausesInstantScroll) { // Disable animated scrolling LayerTreeSettings settings = DefaultSettings(); settings.enable_smooth_scroll = false; @@ -4574,7 +4710,8 @@ TEST_F(LayerTreeHostImplTestScrollbarAnimation, NoAnimator) { RunTest(LayerTreeSettings::NO_ANIMATOR); } -class LayerTreeHostImplTestScrollbarOpacity : public LayerTreeHostImplTest { +class LayerTreeHostImplTestScrollbarOpacity + : public ScrollUnifiedLayerTreeHostImplTest { protected: void RunTest(LayerTreeSettings::ScrollbarAnimator animator) { LayerTreeSettings settings = DefaultSettings(); @@ -4663,19 +4800,24 @@ class LayerTreeHostImplTestScrollbarOpacity : public LayerTreeHostImplTest { } }; -TEST_F(LayerTreeHostImplTestScrollbarOpacity, Android) { +INSTANTIATE_TEST_SUITE_P(All, + LayerTreeHostImplTestScrollbarOpacity, + testing::Bool()); + +TEST_P(LayerTreeHostImplTestScrollbarOpacity, Android) { RunTest(LayerTreeSettings::ANDROID_OVERLAY); } -TEST_F(LayerTreeHostImplTestScrollbarOpacity, AuraOverlay) { +TEST_P(LayerTreeHostImplTestScrollbarOpacity, AuraOverlay) { RunTest(LayerTreeSettings::AURA_OVERLAY); } -TEST_F(LayerTreeHostImplTestScrollbarOpacity, NoAnimator) { +TEST_P(LayerTreeHostImplTestScrollbarOpacity, NoAnimator) { RunTest(LayerTreeSettings::NO_ANIMATOR); } -class LayerTreeHostImplTestMultiScrollable : public LayerTreeHostImplTest { +class LayerTreeHostImplTestMultiScrollable + : public ScrollUnifiedLayerTreeHostImplTest { public: void SetUpLayers(LayerTreeSettings settings) { is_aura_scrollbar_ = @@ -4732,7 +4874,11 @@ class LayerTreeHostImplTestMultiScrollable : public LayerTreeHostImplTest { SolidColorScrollbarLayerImpl* scrollbar_2_; }; -TEST_F(LayerTreeHostImplTestMultiScrollable, +INSTANTIATE_TEST_SUITE_P(All, + LayerTreeHostImplTestMultiScrollable, + testing::Bool()); + +TEST_P(LayerTreeHostImplTestMultiScrollable, ScrollbarFlashAfterAnyScrollUpdate) { LayerTreeSettings settings = DefaultSettings(); settings.scrollbar_fade_delay = base::TimeDelta::FromMilliseconds(500); @@ -4777,7 +4923,7 @@ TEST_F(LayerTreeHostImplTestMultiScrollable, EXPECT_FALSE(animation_task_.is_null()); } -TEST_F(LayerTreeHostImplTestMultiScrollable, ScrollbarFlashWhenMouseEnter) { +TEST_P(LayerTreeHostImplTestMultiScrollable, ScrollbarFlashWhenMouseEnter) { LayerTreeSettings settings = DefaultSettings(); settings.scrollbar_fade_delay = base::TimeDelta::FromMilliseconds(500); settings.scrollbar_fade_duration = base::TimeDelta::FromMilliseconds(300); @@ -4803,7 +4949,7 @@ TEST_F(LayerTreeHostImplTestMultiScrollable, ScrollbarFlashWhenMouseEnter) { EXPECT_FALSE(animation_task_.is_null()); } -TEST_F(LayerTreeHostImplTest, ScrollHitTestOnScrollbar) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollHitTestOnScrollbar) { LayerTreeSettings settings = DefaultSettings(); settings.scrollbar_fade_delay = base::TimeDelta::FromMilliseconds(500); settings.scrollbar_fade_duration = base::TimeDelta::FromMilliseconds(300); @@ -4892,7 +5038,8 @@ TEST_F(LayerTreeHostImplTest, ScrollHitTestOnScrollbar) { } } -TEST_F(LayerTreeHostImplTest, ScrollbarVisibilityChangeCausesRedrawAndCommit) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ScrollbarVisibilityChangeCausesRedrawAndCommit) { LayerTreeSettings settings = DefaultSettings(); settings.scrollbar_animator = LayerTreeSettings::AURA_OVERLAY; settings.scrollbar_fade_delay = base::TimeDelta::FromMilliseconds(20); @@ -4961,7 +5108,7 @@ TEST_F(LayerTreeHostImplTest, ScrollbarVisibilityChangeCausesRedrawAndCommit) { } } -TEST_F(LayerTreeHostImplTest, ScrollbarInnerLargerThanOuter) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollbarInnerLargerThanOuter) { LayerTreeSettings settings = DefaultSettings(); CreateHostImpl(settings, CreateLayerTreeFrameSink()); @@ -4983,7 +5130,7 @@ TEST_F(LayerTreeHostImplTest, ScrollbarInnerLargerThanOuter) { EXPECT_EQ(300, horiz_scrollbar->clip_layer_length()); } -TEST_F(LayerTreeHostImplTest, ScrollbarRegistration) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollbarRegistration) { LayerTreeSettings settings = DefaultSettings(); settings.scrollbar_animator = LayerTreeSettings::ANDROID_OVERLAY; settings.scrollbar_fade_delay = base::TimeDelta::FromMilliseconds(20); @@ -5077,7 +5224,7 @@ TEST_F(LayerTreeHostImplTest, ScrollbarRegistration) { root_scroll_element_id)); } -TEST_F(LayerTreeHostImplTest, ScrollBeforeMouseMove) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollBeforeMouseMove) { LayerTreeSettings settings = DefaultSettings(); settings.scrollbar_animator = LayerTreeSettings::AURA_OVERLAY; settings.scrollbar_fade_delay = base::TimeDelta::FromMilliseconds(20); @@ -5218,18 +5365,18 @@ void LayerTreeHostImplTest::SetupMouseMoveAtWithDeviceScale( scrollbar_animation_controller->MouseIsOverScrollbarThumb(VERTICAL)); } -TEST_F(LayerTreeHostImplTest, MouseMoveAtWithDeviceScaleOf1) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, MouseMoveAtWithDeviceScaleOf1) { SetupMouseMoveAtWithDeviceScale(1); } -TEST_F(LayerTreeHostImplTest, MouseMoveAtWithDeviceScaleOf2) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, MouseMoveAtWithDeviceScaleOf2) { SetupMouseMoveAtWithDeviceScale(2); } // This test verifies that only SurfaceLayers in the viewport and have fallbacks // that are different are included in viz::CompositorFrameMetadata's // |activation_dependencies|. -TEST_F(LayerTreeHostImplTest, ActivationDependenciesInMetadata) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ActivationDependenciesInMetadata) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); LayerImpl* root = root_layer(); @@ -5308,7 +5455,8 @@ TEST_F(LayerTreeHostImplTest, ActivationDependenciesInMetadata) { // Verify that updating the set of referenced surfaces for the active tree // causes a new CompositorFrame to be submitted, even if there is no other // damage. -TEST_F(LayerTreeHostImplTest, SurfaceReferencesChangeCausesDamage) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + SurfaceReferencesChangeCausesDamage) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); auto* fake_layer_tree_frame_sink = static_cast<FakeLayerTreeFrameSink*>(host_impl_->layer_tree_frame_sink()); @@ -5340,7 +5488,7 @@ TEST_F(LayerTreeHostImplTest, SurfaceReferencesChangeCausesDamage) { } } -TEST_F(LayerTreeHostImplTest, CompositorFrameMetadata) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, CompositorFrameMetadata) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); host_impl_->active_tree()->PushPageScaleFromMainThread(1, 0.5f, 4); DrawFrame(); @@ -5467,7 +5615,8 @@ class DidDrawCheckLayer : public LayerImpl { bool did_draw_called_; }; -TEST_F(LayerTreeHostImplTest, DamageShouldNotCareAboutContributingLayers) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + DamageShouldNotCareAboutContributingLayers) { auto* root = SetupRootLayer<DidDrawCheckLayer>(host_impl_->active_tree(), gfx::Size(10, 10)); @@ -5551,7 +5700,7 @@ TEST_F(LayerTreeHostImplTest, DamageShouldNotCareAboutContributingLayers) { } } -TEST_F(LayerTreeHostImplTest, WillDrawReturningFalseDoesNotCall) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, WillDrawReturningFalseDoesNotCall) { // The root layer is always drawn, so run this test on a child layer that // will be masked out by the root layer's bounds. auto* root = SetupRootLayer<DidDrawCheckLayer>(host_impl_->active_tree(), @@ -5574,7 +5723,7 @@ TEST_F(LayerTreeHostImplTest, WillDrawReturningFalseDoesNotCall) { EXPECT_FALSE(layer->did_draw_called()); } -TEST_F(LayerTreeHostImplTest, DidDrawNotCalledOnHiddenLayer) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, DidDrawNotCalledOnHiddenLayer) { // The root layer is always drawn, so run this test on a child layer that // will be masked out by the root layer's bounds. auto* root = SetupRootLayer<DidDrawCheckLayer>(host_impl_->active_tree(), @@ -5613,7 +5762,7 @@ TEST_F(LayerTreeHostImplTest, DidDrawNotCalledOnHiddenLayer) { EXPECT_FALSE(layer->visible_layer_rect().IsEmpty()); } -TEST_F(LayerTreeHostImplTest, WillDrawNotCalledOnOccludedLayer) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, WillDrawNotCalledOnOccludedLayer) { gfx::Size big_size(1000, 1000); auto* root = SetupRootLayer<DidDrawCheckLayer>(host_impl_->active_tree(), big_size); @@ -5641,7 +5790,7 @@ TEST_F(LayerTreeHostImplTest, WillDrawNotCalledOnOccludedLayer) { EXPECT_TRUE(top_layer->did_draw_called()); } -TEST_F(LayerTreeHostImplTest, DidDrawCalledOnAllLayers) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, DidDrawCalledOnAllLayers) { auto* root = SetupRootLayer<DidDrawCheckLayer>(host_impl_->active_tree(), gfx::Size(10, 10)); auto* layer1 = AddLayer<DidDrawCheckLayer>(host_impl_->active_tree()); @@ -5906,11 +6055,12 @@ TEST_F(LayerTreeHostImplPrepareToDrawTest, } } -TEST_F(LayerTreeHostImplTest, ScrollRootIgnored) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollRootIgnored) { SetupDefaultRootLayer(gfx::Size(10, 10)); DrawFrame(); - // Scroll event is ignored because layer is not scrollable. + // Scroll event is ignored because layer is not scrollable and there is no + // viewport. InputHandler::ScrollStatus status = host_impl_->ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, 10), ui::ScrollInputType::kWheel) @@ -5923,7 +6073,7 @@ TEST_F(LayerTreeHostImplTest, ScrollRootIgnored) { EXPECT_FALSE(did_request_commit_); } -TEST_F(LayerTreeHostImplTest, ClampingAfterActivation) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ClampingAfterActivation) { CreatePendingTree(); host_impl_->pending_tree()->PushPageScaleFromMainThread(1, 1, 1); SetupViewportLayers(host_impl_->pending_tree(), gfx::Size(50, 50), @@ -5945,7 +6095,8 @@ TEST_F(LayerTreeHostImplTest, ClampingAfterActivation) { EXPECT_EQ(CurrentScrollOffset(active_outer_layer), gfx::ScrollOffset(0, 0)); } -class LayerTreeHostImplBrowserControlsTest : public LayerTreeHostImplTest { +class LayerTreeHostImplBrowserControlsTest + : public ScrollUnifiedLayerTreeHostImplTest { public: LayerTreeHostImplBrowserControlsTest() // Make the clip size the same as the layer (content) size so the layer is @@ -6002,6 +6153,10 @@ class LayerTreeHostImplBrowserControlsTest : public LayerTreeHostImplTest { LayerTreeSettings settings_; }; // class LayerTreeHostImplBrowserControlsTest +INSTANTIATE_TEST_SUITE_P(All, + LayerTreeHostImplBrowserControlsTest, + testing::Bool()); + #define EXPECT_VIEWPORT_GEOMETRIES(expected_browser_controls_shown_ratio) \ do { \ auto* tree = host_impl_->active_tree(); \ @@ -6031,7 +6186,7 @@ class LayerTreeHostImplBrowserControlsTest : public LayerTreeHostImplTest { // size). Since the viewport got larger, the effective scrollable "content" also // did. This ensures, for one thing, that the overscroll glow is shown in the // right place. -TEST_F(LayerTreeHostImplBrowserControlsTest, +TEST_P(LayerTreeHostImplBrowserControlsTest, HidingBrowserControlsExpandsScrollableSize) { SetupBrowserControlsAndScrollLayerWithVirtualViewport( gfx::Size(50, 50), gfx::Size(50, 50), gfx::Size(50, 50)); @@ -6074,11 +6229,108 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, host_impl_->ScrollEnd(); } +TEST_P(LayerTreeHostImplBrowserControlsTest, + HidingBrowserControlsExpandsClipAncestorsOfReplacedOuterScroller) { + SetupBrowserControlsAndScrollLayerWithVirtualViewport( + gfx::Size(180, 180), gfx::Size(180, 180), gfx::Size(180, 180)); + + LayerTreeImpl* active_tree = host_impl_->active_tree(); + PropertyTrees* property_trees = active_tree->property_trees(); + LayerImpl* original_outer_scroll = OuterViewportScrollLayer(); + + LayerImpl* parent_clip_layer = AddLayer(); + CopyProperties(original_outer_scroll, parent_clip_layer); + parent_clip_layer->SetBounds(gfx::Size(160, 160)); + CreateClipNode(parent_clip_layer); + LayerImpl* clip_layer = AddLayer(); + clip_layer->SetBounds(gfx::Size(150, 150)); + CopyProperties(parent_clip_layer, clip_layer); + CreateClipNode(clip_layer); + LayerImpl* scroll_layer = + AddScrollableLayer(clip_layer, gfx::Size(150, 150), gfx::Size(300, 300)); + GetScrollNode(scroll_layer)->scrolls_outer_viewport = true; + ClipNode* original_outer_clip = GetClipNode(original_outer_scroll); + ClipNode* parent_clip = GetClipNode(parent_clip_layer); + ClipNode* scroll_clip = GetClipNode(clip_layer); + + auto viewport_property_ids = active_tree->ViewportPropertyIdsForTesting(); + viewport_property_ids.outer_clip = clip_layer->clip_tree_index(); + viewport_property_ids.outer_scroll = scroll_layer->scroll_tree_index(); + active_tree->SetViewportPropertyIds(viewport_property_ids); + UpdateDrawProperties(active_tree); + + EXPECT_EQ(scroll_layer, OuterViewportScrollLayer()); + EXPECT_EQ(GetScrollNode(scroll_layer), + active_tree->OuterViewportScrollNode()); + + EXPECT_EQ(1, active_tree->CurrentTopControlsShownRatio()); + EXPECT_EQ(50, host_impl_->browser_controls_manager()->ContentTopOffset()); + EXPECT_EQ(gfx::Vector2dF(), + property_trees->inner_viewport_container_bounds_delta()); + EXPECT_EQ(gfx::Vector2dF(), + property_trees->outer_viewport_container_bounds_delta()); + EXPECT_EQ(gfx::SizeF(300, 300), active_tree->ScrollableSize()); + EXPECT_EQ(gfx::RectF(0, 0, 180, 180), original_outer_clip->clip); + EXPECT_EQ(gfx::RectF(0, 0, 160, 160), parent_clip->clip); + EXPECT_EQ(gfx::RectF(0, 0, 150, 150), scroll_clip->clip); + + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, + host_impl_ + ->ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, 25), + ui::ScrollInputType::kTouchscreen) + .get(), + ui::ScrollInputType::kTouchscreen) + .thread); + + // Hide the browser controls by a bit, the scrollable size should increase but + // the actual content bounds shouldn't. + host_impl_->browser_controls_manager()->ScrollBy(gfx::Vector2dF(0, 25)); + EXPECT_EQ(0.5f, active_tree->CurrentTopControlsShownRatio()); + EXPECT_EQ(25, host_impl_->browser_controls_manager()->ContentTopOffset()); + EXPECT_EQ(gfx::Vector2dF(0, 25), + property_trees->inner_viewport_container_bounds_delta()); + EXPECT_EQ(gfx::Vector2dF(0, 25), + property_trees->outer_viewport_container_bounds_delta()); + EXPECT_EQ(gfx::SizeF(300, 300), active_tree->ScrollableSize()); + EXPECT_EQ(gfx::RectF(0, 0, 150, 175), scroll_clip->clip); + EXPECT_EQ(gfx::RectF(0, 0, 160, 175), parent_clip->clip); + EXPECT_EQ(gfx::RectF(0, 0, 180, 180), original_outer_clip->clip); + + // Fully hide the browser controls. + host_impl_->browser_controls_manager()->ScrollBy(gfx::Vector2dF(0, 25)); + EXPECT_EQ(0, active_tree->CurrentTopControlsShownRatio()); + EXPECT_EQ(0, host_impl_->browser_controls_manager()->ContentTopOffset()); + EXPECT_EQ(gfx::Vector2dF(0, 50), + property_trees->inner_viewport_container_bounds_delta()); + EXPECT_EQ(gfx::Vector2dF(0, 50), + property_trees->outer_viewport_container_bounds_delta()); + EXPECT_EQ(gfx::SizeF(300, 300), active_tree->ScrollableSize()); + EXPECT_EQ(gfx::RectF(0, 0, 150, 200), scroll_clip->clip); + EXPECT_EQ(gfx::RectF(0, 0, 160, 200), parent_clip->clip); + EXPECT_EQ(gfx::RectF(0, 0, 180, 200), original_outer_clip->clip); + + // Scrolling additionally shouldn't have any effect. + host_impl_->browser_controls_manager()->ScrollBy(gfx::Vector2dF(0, 25)); + EXPECT_EQ(0, active_tree->CurrentTopControlsShownRatio()); + EXPECT_EQ(0, host_impl_->browser_controls_manager()->ContentTopOffset()); + EXPECT_EQ(gfx::Vector2dF(0, 50), + property_trees->inner_viewport_container_bounds_delta()); + EXPECT_EQ(gfx::Vector2dF(0, 50), + property_trees->outer_viewport_container_bounds_delta()); + EXPECT_EQ(gfx::SizeF(300, 300), active_tree->ScrollableSize()); + EXPECT_EQ(gfx::RectF(0, 0, 150, 200), scroll_clip->clip); + EXPECT_EQ(gfx::RectF(0, 0, 160, 200), parent_clip->clip); + EXPECT_EQ(gfx::RectF(0, 0, 180, 200), original_outer_clip->clip); + + host_impl_->browser_controls_manager()->ScrollEnd(); + host_impl_->ScrollEnd(); +} + // Ensure that moving the browser controls (i.e. omnibox/url-bar on mobile) on // pages with a non-1 minimum page scale factor (e.g. legacy desktop page) // correctly scales the clipping adjustment performed to show the newly exposed // region of the page. -TEST_F(LayerTreeHostImplBrowserControlsTest, +TEST_P(LayerTreeHostImplBrowserControlsTest, MovingBrowserControlsOuterClipDeltaScaled) { gfx::Size inner_size = gfx::Size(100, 100); gfx::Size outer_size = gfx::Size(100, 100); @@ -6122,7 +6374,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, } // Tests that browser controls affect the position of horizontal scrollbars. -TEST_F(LayerTreeHostImplBrowserControlsTest, +TEST_P(LayerTreeHostImplBrowserControlsTest, HidingBrowserControlsAdjustsScrollbarPosition) { SetupBrowserControlsAndScrollLayerWithVirtualViewport( gfx::Size(50, 50), gfx::Size(50, 50), gfx::Size(50, 50)); @@ -6198,7 +6450,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, host_impl_->ScrollEnd(); } -TEST_F(LayerTreeHostImplBrowserControlsTest, +TEST_P(LayerTreeHostImplBrowserControlsTest, ScrollBrowserControlsByFractionalAmount) { SetupBrowserControlsAndScrollLayerWithVirtualViewport( gfx::Size(10, 10), gfx::Size(10, 10), gfx::Size(10, 10)); @@ -6231,7 +6483,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, // scroll initiated on the inner viewport, causing the browser controls to show // and thus making the outer viewport scrollable, still scrolls the outer // viewport. -TEST_F(LayerTreeHostImplBrowserControlsTest, +TEST_P(LayerTreeHostImplBrowserControlsTest, BrowserControlsOuterViewportBecomesScrollable) { SetupBrowserControlsAndScrollLayerWithVirtualViewport( gfx::Size(10, 50), gfx::Size(10, 50), gfx::Size(10, 100)); @@ -6322,7 +6574,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, // Test that the fixed position container delta is appropriately adjusted // by the browser controls showing/hiding and page scale doesn't affect it. -TEST_F(LayerTreeHostImplBrowserControlsTest, FixedContainerDelta) { +TEST_P(LayerTreeHostImplBrowserControlsTest, FixedContainerDelta) { SetupBrowserControlsAndScrollLayerWithVirtualViewport( gfx::Size(100, 100), gfx::Size(100, 100), gfx::Size(100, 100)); DrawFrame(); @@ -6396,7 +6648,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, FixedContainerDelta) { // Push a browser controls ratio from the main thread that we didn't send as a // delta and make sure that the ratio is clamped to the [0, 1] range. -TEST_F(LayerTreeHostImplBrowserControlsTest, BrowserControlsPushUnsentRatio) { +TEST_P(LayerTreeHostImplBrowserControlsTest, BrowserControlsPushUnsentRatio) { SetupBrowserControlsAndScrollLayerWithVirtualViewport( gfx::Size(10, 50), gfx::Size(10, 50), gfx::Size(10, 100)); DrawFrame(); @@ -6420,7 +6672,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, BrowserControlsPushUnsentRatio) { // Test that if a scrollable sublayer doesn't consume the scroll, // browser controls should hide when scrolling down. -TEST_F(LayerTreeHostImplBrowserControlsTest, +TEST_P(LayerTreeHostImplBrowserControlsTest, BrowserControlsScrollableSublayer) { gfx::Size sub_content_size(100, 400); gfx::Size sub_content_layer_size(100, 300); @@ -6470,7 +6722,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, // Ensure setting the browser controls position explicitly using the setters on // the TreeImpl correctly affects the browser controls manager and viewport // bounds for the active tree. -TEST_F(LayerTreeHostImplBrowserControlsTest, +TEST_P(LayerTreeHostImplBrowserControlsTest, PositionBrowserControlsToActiveTreeExplicitly) { SetupBrowserControlsAndScrollLayerWithVirtualViewport( layer_size_, layer_size_, layer_size_); @@ -6501,7 +6753,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, // Ensure setting the browser controls position explicitly using the setters on // the TreeImpl correctly affects the browser controls manager and viewport // bounds for the pending tree. -TEST_F(LayerTreeHostImplBrowserControlsTest, +TEST_P(LayerTreeHostImplBrowserControlsTest, PositionBrowserControlsToPendingTreeExplicitly) { CreatePendingTree(); SetupBrowserControlsAndScrollLayerWithVirtualViewport( @@ -6546,7 +6798,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, // Test that the top_controls delta and sent delta are appropriately // applied on sync tree activation. The total browser controls offset shouldn't // change after the activation. -TEST_F(LayerTreeHostImplBrowserControlsTest, ApplyDeltaOnTreeActivation) { +TEST_P(LayerTreeHostImplBrowserControlsTest, ApplyDeltaOnTreeActivation) { SetupBrowserControlsAndScrollLayerWithVirtualViewport( layer_size_, layer_size_, layer_size_); DrawFrame(); @@ -6586,7 +6838,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, ApplyDeltaOnTreeActivation) { // the inner viewport container bounds. That is, the browser controls layout // height is the amount that the inner viewport container was shrunk outside // the compositor to accommodate the browser controls. -TEST_F(LayerTreeHostImplBrowserControlsTest, +TEST_P(LayerTreeHostImplBrowserControlsTest, BrowserControlsLayoutHeightChanged) { SetupBrowserControlsAndScrollLayerWithVirtualViewport( layer_size_, layer_size_, layer_size_); @@ -6629,7 +6881,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, // Test that showing/hiding the browser controls when the viewport is fully // scrolled doesn't incorrectly change the viewport offset due to clamping from // changing viewport bounds. -TEST_F(LayerTreeHostImplBrowserControlsTest, +TEST_P(LayerTreeHostImplBrowserControlsTest, BrowserControlsViewportOffsetClamping) { SetupBrowserControlsAndScrollLayerWithVirtualViewport( gfx::Size(100, 100), gfx::Size(200, 200), gfx::Size(200, 400)); @@ -6705,7 +6957,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, // Test that the browser controls coming in and out maintains the same aspect // ratio between the inner and outer viewports. -TEST_F(LayerTreeHostImplBrowserControlsTest, BrowserControlsAspectRatio) { +TEST_P(LayerTreeHostImplBrowserControlsTest, BrowserControlsAspectRatio) { SetupBrowserControlsAndScrollLayerWithVirtualViewport( gfx::Size(100, 100), gfx::Size(200, 200), gfx::Size(200, 400)); host_impl_->active_tree()->PushPageScaleFromMainThread(1, 0.5f, 2); @@ -6745,7 +6997,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, BrowserControlsAspectRatio) { } // Test that scrolling the outer viewport affects the browser controls. -TEST_F(LayerTreeHostImplBrowserControlsTest, +TEST_P(LayerTreeHostImplBrowserControlsTest, BrowserControlsScrollOuterViewport) { SetupBrowserControlsAndScrollLayerWithVirtualViewport( gfx::Size(100, 100), gfx::Size(200, 200), gfx::Size(200, 400)); @@ -6821,7 +7073,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, host_impl_->ScrollEnd(); } -TEST_F(LayerTreeHostImplBrowserControlsTest, +TEST_P(LayerTreeHostImplBrowserControlsTest, ScrollNonScrollableRootWithBrowserControls) { SetupBrowserControlsAndScrollLayerWithVirtualViewport( layer_size_, layer_size_, layer_size_); @@ -6892,7 +7144,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, // was occurring because the UpdateViewportContainerSizes was being called // before the property trees were updated with the bounds_delta. // crbug.com/597266. -TEST_F(LayerTreeHostImplBrowserControlsTest, +TEST_P(LayerTreeHostImplBrowserControlsTest, ViewportBoundsDeltaOnTreeActivation) { const gfx::Size inner_viewport_size(1000, 1000); const gfx::Size outer_viewport_size(1000, 1000); @@ -7016,7 +7268,7 @@ TEST_F(LayerTreeHostImplBrowserControlsTest, } } -TEST_F(LayerTreeHostImplTest, ScrollNonCompositedRoot) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollNonCompositedRoot) { // Test the configuration where a non-composited root layer is embedded in a // scrollable outer layer. gfx::Size surface_size(10, 10); @@ -7054,7 +7306,7 @@ TEST_F(LayerTreeHostImplTest, ScrollNonCompositedRoot) { EXPECT_TRUE(did_request_commit_); } -TEST_F(LayerTreeHostImplTest, ScrollChildCallsCommitAndRedraw) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollChildCallsCommitAndRedraw) { gfx::Size surface_size(10, 10); gfx::Size contents_size(20, 20); @@ -7083,33 +7335,35 @@ TEST_F(LayerTreeHostImplTest, ScrollChildCallsCommitAndRedraw) { EXPECT_TRUE(did_request_commit_); } -TEST_F(LayerTreeHostImplTest, ScrollMissesChild) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollMissesChild) { gfx::Size viewport_size(5, 5); gfx::Size surface_size(10, 10); - LayerImpl* root = SetupDefaultRootLayer(surface_size); - AddScrollableLayer(root, viewport_size, surface_size); + SetupViewportLayersOuterScrolls(viewport_size, surface_size); + AddScrollableLayer(OuterViewportScrollLayer(), viewport_size, surface_size); DrawFrame(); - // Scroll event is ignored because the input coordinate is outside the layer - // boundaries. + // A scroll that doesn't hit any layers should fall back to viewport + // scrolling. InputHandler::ScrollStatus status = host_impl_->ScrollBegin( BeginState(gfx::Point(15, 5), gfx::Vector2dF(0, 10), ui::ScrollInputType::kWheel) .get(), ui::ScrollInputType::kWheel); - EXPECT_EQ(InputHandler::SCROLL_IGNORED, status.thread); - EXPECT_EQ(MainThreadScrollingReason::kNoScrollingLayer, - status.main_thread_scrolling_reasons); + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_EQ(host_impl_->CurrentlyScrollingNode(), + host_impl_->OuterViewportScrollNode()); EXPECT_FALSE(did_request_redraw_); EXPECT_FALSE(did_request_commit_); } -TEST_F(LayerTreeHostImplTest, ScrollMissesBackfacingChild) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollMissesBackfacingChild) { gfx::Size viewport_size(5, 5); gfx::Size surface_size(10, 10); - LayerImpl* root = SetupDefaultRootLayer(viewport_size); - LayerImpl* child = AddScrollableLayer(root, viewport_size, surface_size); + + SetupViewportLayersOuterScrolls(viewport_size, surface_size); + LayerImpl* child = AddScrollableLayer(OuterViewportScrollLayer(), + viewport_size, surface_size); gfx::Transform matrix; matrix.RotateAboutXAxis(180.0); @@ -7118,22 +7372,22 @@ TEST_F(LayerTreeHostImplTest, ScrollMissesBackfacingChild) { CreateEffectNode(child).double_sided = false; DrawFrame(); - // Scroll event is ignored because the scrollable layer is not facing the - // viewer and there is nothing scrollable behind it. + // The scroll shouldn't hit the child layer because the it isn't facing the + // viewer. The hit test should go through it and hit the outer viewport. InputHandler::ScrollStatus status = host_impl_->ScrollBegin( BeginState(gfx::Point(5, 5), gfx::Vector2dF(0, 10), ui::ScrollInputType::kWheel) .get(), ui::ScrollInputType::kWheel); - EXPECT_EQ(InputHandler::SCROLL_IGNORED, status.thread); - EXPECT_EQ(MainThreadScrollingReason::kNoScrollingLayer, - status.main_thread_scrolling_reasons); + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_EQ(host_impl_->CurrentlyScrollingNode(), + host_impl_->OuterViewportScrollNode()); EXPECT_FALSE(did_request_redraw_); EXPECT_FALSE(did_request_commit_); } -TEST_F(LayerTreeHostImplTest, ScrollBlockedByContentLayer) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollLayerWithMainThreadReason) { gfx::Size scroll_container_size(5, 5); gfx::Size surface_size(10, 10); LayerImpl* root = SetupDefaultRootLayer(surface_size); @@ -7148,19 +7402,28 @@ TEST_F(LayerTreeHostImplTest, ScrollBlockedByContentLayer) { MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects; DrawFrame(); - // Scrolling fails because the content layer is asking to be scrolled on the - // main thread. InputHandler::ScrollStatus status = host_impl_->ScrollBegin( BeginState(gfx::Point(5, 5), gfx::Vector2dF(0, 10), ui::ScrollInputType::kWheel) .get(), ui::ScrollInputType::kWheel); - EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); - EXPECT_EQ(MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects, - status.main_thread_scrolling_reasons); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_FALSE(status.needs_main_thread_hit_test); + EXPECT_EQ( + MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects, + host_impl_->CurrentlyScrollingNode()->main_thread_scrolling_reasons); + } else { + // Scrolling fails because the content layer is asking to be scrolled on the + // main thread. + EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects, + status.main_thread_scrolling_reasons); + } } -TEST_F(LayerTreeHostImplTest, ScrollRootAndChangePageScaleOnMainThread) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ScrollRootAndChangePageScaleOnMainThread) { gfx::Size inner_viewport_size(20, 20); gfx::Size outer_viewport_size(40, 40); gfx::Size content_size(80, 80); @@ -7203,7 +7466,8 @@ TEST_F(LayerTreeHostImplTest, ScrollRootAndChangePageScaleOnMainThread) { EXPECT_EQ(1, host_impl_->active_tree()->page_scale_delta()); } -TEST_F(LayerTreeHostImplTest, ScrollRootAndChangePageScaleOnImplThread) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ScrollRootAndChangePageScaleOnImplThread) { gfx::Size inner_viewport_size(20, 20); gfx::Size outer_viewport_size(40, 40); gfx::Size content_size(80, 80); @@ -7255,7 +7519,8 @@ TEST_F(LayerTreeHostImplTest, ScrollRootAndChangePageScaleOnImplThread) { EXPECT_EQ(page_scale, host_impl_->active_tree()->current_page_scale_factor()); } -TEST_F(LayerTreeHostImplTest, PageScaleDeltaAppliedToRootScrollLayerOnly) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + PageScaleDeltaAppliedToRootScrollLayerOnly) { host_impl_->active_tree()->PushPageScaleFromMainThread(1, 1, 2); gfx::Size viewport_size(5, 5); gfx::Size surface_size(10, 10); @@ -7305,7 +7570,8 @@ TEST_F(LayerTreeHostImplTest, PageScaleDeltaAppliedToRootScrollLayerOnly) { outer_scroll->DrawTransform().matrix().getDouble(1, 1)); } -TEST_F(LayerTreeHostImplTest, ScrollChildAndChangePageScaleOnMainThread) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ScrollChildAndChangePageScaleOnMainThread) { SetupViewportLayers(host_impl_->active_tree(), gfx::Size(15, 15), gfx::Size(30, 30), gfx::Size(50, 50)); LayerImpl* outer_scroll = OuterViewportScrollLayer(); @@ -7345,7 +7611,7 @@ TEST_F(LayerTreeHostImplTest, ScrollChildAndChangePageScaleOnMainThread) { EXPECT_EQ(1, host_impl_->active_tree()->page_scale_delta()); } -TEST_F(LayerTreeHostImplTest, ScrollChildBeyondLimit) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollChildBeyondLimit) { // Scroll a child layer beyond its maximum scroll range and make sure the // parent layer isn't scrolled. gfx::Size surface_size(10, 10); @@ -7490,7 +7756,7 @@ TEST_F(LayerTreeHostImplTimelinesTest, ScrollAnimatedLatchToChild) { host_impl_ = nullptr; } -TEST_F(LayerTreeHostImplTest, ScrollWithoutBubbling) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollWithoutBubbling) { // Scroll a child layer beyond its maximum scroll range and make sure the // the scroll doesn't bubble up to the parent layer. gfx::Size surface_size(20, 20); @@ -7629,7 +7895,8 @@ TEST_F(LayerTreeHostImplTest, ScrollWithoutBubbling) { // Ensure that layers who's scroll parent is the InnerViewportScrollNode are // still able to scroll on the compositor. -TEST_F(LayerTreeHostImplTest, ChildrenOfInnerScrollNodeCanScrollOnThread) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ChildrenOfInnerScrollNodeCanScrollOnThread) { gfx::Size viewport_size(10, 10); gfx::Size content_size(20, 20); SetupViewportLayersOuterScrolls(viewport_size, content_size); @@ -7673,7 +7940,7 @@ TEST_F(LayerTreeHostImplTest, ChildrenOfInnerScrollNodeCanScrollOnThread) { } } -TEST_F(LayerTreeHostImplTest, ScrollEventBubbling) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollEventBubbling) { // When we try to scroll a non-scrollable child layer, the scroll delta // should be applied to one of its ancestors if possible. gfx::Size viewport_size(10, 10); @@ -7716,7 +7983,7 @@ TEST_F(LayerTreeHostImplTest, ScrollEventBubbling) { } } -TEST_F(LayerTreeHostImplTest, ScrollBeforeRedraw) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollBeforeRedraw) { gfx::Size surface_size(10, 10); SetupViewportLayersNoScrolls(surface_size); UpdateDrawProperties(host_impl_->active_tree()); @@ -7740,7 +8007,7 @@ TEST_F(LayerTreeHostImplTest, ScrollBeforeRedraw) { .thread); } -TEST_F(LayerTreeHostImplTest, ScrollAxisAlignedRotatedLayer) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollAxisAlignedRotatedLayer) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); auto* scroll_layer = InnerViewportScrollLayer(); scroll_layer->SetDrawsContent(true); @@ -7798,7 +8065,7 @@ TEST_F(LayerTreeHostImplTest, ScrollAxisAlignedRotatedLayer) { wheel_scroll_delta)); } -TEST_F(LayerTreeHostImplTest, ScrollNonAxisAlignedRotatedLayer) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollNonAxisAlignedRotatedLayer) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); auto* scroll_layer = InnerViewportScrollLayer(); float child_layer_angle = -20; @@ -7891,7 +8158,7 @@ TEST_F(LayerTreeHostImplTest, ScrollNonAxisAlignedRotatedLayer) { } } -TEST_F(LayerTreeHostImplTest, ScrollPerspectiveTransformedLayer) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollPerspectiveTransformedLayer) { // When scrolling an element with perspective, the distance scrolled // depends on the point at which the scroll begins. SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); @@ -7973,7 +8240,7 @@ TEST_F(LayerTreeHostImplTest, ScrollPerspectiveTransformedLayer) { } } -TEST_F(LayerTreeHostImplTest, ScrollScaledLayer) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollScaledLayer) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); auto* scroll_layer = InnerViewportScrollLayer(); @@ -8032,7 +8299,7 @@ TEST_F(LayerTreeHostImplTest, ScrollScaledLayer) { wheel_scroll_delta)); } -TEST_F(LayerTreeHostImplTest, ScrollViewportRounding) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollViewportRounding) { int width = 332; int height = 20; int scale = 3; @@ -8048,7 +8315,7 @@ TEST_F(LayerTreeHostImplTest, ScrollViewportRounding) { MaxScrollOffset(inner_viewport_scroll_layer)); } -TEST_F(LayerTreeHostImplTest, RootLayerScrollOffsetDelegation) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, RootLayerScrollOffsetDelegation) { TestInputHandlerClient scroll_watcher; SetupViewportLayersInnerScrolls(gfx::Size(10, 20), gfx::Size(100, 100)); auto* scroll_layer = InnerViewportScrollLayer(); @@ -8166,7 +8433,7 @@ void CheckLayerScrollDelta(LayerImpl* layer, gfx::Vector2dF scroll_delta) { EXPECT_EQ(expected_point.ToString(), translated_point.ToString()); } -TEST_F(LayerTreeHostImplTest, +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ExternalRootLayerScrollOffsetDelegationReflectedInNextDraw) { SetupViewportLayersInnerScrolls(gfx::Size(10, 20), gfx::Size(100, 100)); auto* scroll_layer = InnerViewportScrollLayer(); @@ -8201,7 +8468,7 @@ TEST_F(LayerTreeHostImplTest, // Ensure the viewport correctly handles the user_scrollable bits. That is, if // the outer viewport disables user scrolling, we should still be able to // scroll the inner viewport. -TEST_F(LayerTreeHostImplTest, ViewportUserScrollable) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ViewportUserScrollable) { gfx::Size viewport_size(100, 100); gfx::Size content_size(200, 200); SetupViewportLayersOuterScrolls(viewport_size, content_size); @@ -8367,7 +8634,7 @@ TEST_F(LayerTreeHostImplTest, ViewportUserScrollable) { // Ensure that the SetSynchronousInputHandlerRootScrollOffset method used by // the WebView API correctly respects the user_scrollable bits on both of the // inner and outer viewport scroll nodes. -TEST_F(LayerTreeHostImplTest, SetRootScrollOffsetUserScrollable) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, SetRootScrollOffsetUserScrollable) { gfx::Size viewport_size(100, 100); gfx::Size content_size(200, 200); SetupViewportLayersOuterScrolls(viewport_size, content_size); @@ -8480,14 +8747,14 @@ TEST_F(LayerTreeHostImplTest, SetRootScrollOffsetUserScrollable) { // The SetSynchronousInputHandlerRootScrollOffset API can be called while there // is no inner viewport set. This test passes if we don't crash. -TEST_F(LayerTreeHostImplTest, SetRootScrollOffsetNoViewportCrash) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, SetRootScrollOffsetNoViewportCrash) { auto* inner_scroll = InnerViewportScrollLayer(); ASSERT_FALSE(inner_scroll); gfx::ScrollOffset scroll_offset(25, 30); host_impl_->SetSynchronousInputHandlerRootScrollOffset(scroll_offset); } -TEST_F(LayerTreeHostImplTest, OverscrollRoot) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, OverscrollRoot) { InputHandlerScrollResult scroll_result; SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); @@ -8641,7 +8908,7 @@ TEST_F(LayerTreeHostImplTest, OverscrollRoot) { host_impl_->ScrollEnd(); } -TEST_F(LayerTreeHostImplTest, OverscrollChildWithoutBubbling) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, OverscrollChildWithoutBubbling) { // Scroll child layers beyond their maximum scroll range and make sure root // overscroll does not accumulate. InputHandlerScrollResult scroll_result; @@ -8746,7 +9013,7 @@ TEST_F(LayerTreeHostImplTest, OverscrollChildWithoutBubbling) { } } -TEST_F(LayerTreeHostImplTest, OverscrollChildEventBubbling) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, OverscrollChildEventBubbling) { // When we try to scroll a non-scrollable child layer, the scroll delta // should be applied to one of its ancestors if possible. Overscroll should // be reflected only when it has bubbled up to the root scrolling layer. @@ -8784,7 +9051,7 @@ TEST_F(LayerTreeHostImplTest, OverscrollChildEventBubbling) { } } -TEST_F(LayerTreeHostImplTest, OverscrollAlways) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, OverscrollAlways) { InputHandlerScrollResult scroll_result; LayerTreeSettings settings = DefaultSettings(); CreateHostImpl(settings, CreateLayerTreeFrameSink()); @@ -8813,7 +9080,7 @@ TEST_F(LayerTreeHostImplTest, OverscrollAlways) { EXPECT_EQ(gfx::Vector2dF(0, 10), host_impl_->accumulated_root_overscroll()); } -TEST_F(LayerTreeHostImplTest, NoOverscrollWhenNotAtEdge) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, NoOverscrollWhenNotAtEdge) { InputHandlerScrollResult scroll_result; gfx::Size viewport_size(100, 100); gfx::Size content_size(200, 200); @@ -8898,7 +9165,7 @@ TEST_F(LayerTreeHostImplTest, NoOverscrollWhenNotAtEdge) { } } -TEST_F(LayerTreeHostImplTest, NoOverscrollOnNonViewportLayers) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, NoOverscrollOnNonViewportLayers) { const gfx::Size content_size(200, 200); const gfx::Size viewport_size(100, 100); @@ -8963,7 +9230,15 @@ TEST_F(LayerTreeHostImplTest, NoOverscrollOnNonViewportLayers) { host_impl_->ScrollEnd(); } -TEST_F(LayerTreeHostImplTest, OverscrollOnMainThread) { +// This ensures that the --disable-threaded-scrolling flag is respected even +// when a scroll occurs outside of a layer which normally falls back to the +// inner viewport layer. https://crbug.com/625100. +TEST_P(ScrollUnifiedLayerTreeHostImplTest, OverscrollOnMainThread) { + // TODO(bokan): The meaning of this flag is lost with scroll unification. We + // need to decide what we should do with it. https://crbug.com/1086625. + if (base::FeatureList::IsEnabled(features::kScrollUnification)) + return; + InputHandlerScrollResult scroll_result; LayerTreeSettings settings = DefaultSettings(); CreateHostImpl(settings, CreateLayerTreeFrameSink()); @@ -9005,7 +9280,7 @@ TEST_F(LayerTreeHostImplTest, OverscrollOnMainThread) { // Test that scrolling the inner viewport directly works, as can happen when the // scroll chains up to it from an sibling of the outer viewport. -TEST_F(LayerTreeHostImplTest, ScrollFromOuterViewportSibling) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollFromOuterViewportSibling) { const gfx::Size viewport_size(100, 100); SetupViewportLayersNoScrolls(viewport_size); @@ -9081,7 +9356,8 @@ TEST_F(LayerTreeHostImplTest, ScrollFromOuterViewportSibling) { // Test that scrolls chain correctly when a child scroller on the page (e.g. a // scrolling div) is set as the outer viewport. This happens in the // rootScroller proposal. -TEST_F(LayerTreeHostImplTest, ScrollChainingWithReplacedOuterViewport) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ScrollChainingWithReplacedOuterViewport) { const gfx::Size content_size(200, 200); const gfx::Size viewport_size(100, 100); @@ -9211,7 +9487,7 @@ TEST_F(LayerTreeHostImplTest, ScrollChainingWithReplacedOuterViewport) { // scrolling div) is set as the outer viewport but scrolls start from a layer // that's not a descendant of the outer viewport. This happens in the // rootScroller proposal. -TEST_F(LayerTreeHostImplTest, RootScrollerScrollNonDescendant) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, RootScrollerScrollNonDescendant) { const gfx::Size content_size(300, 300); const gfx::Size viewport_size(300, 300); @@ -9381,7 +9657,7 @@ TEST_F(LayerTreeHostImplTest, RootScrollerScrollNonDescendant) { } } -TEST_F(LayerTreeHostImplTest, OverscrollOnImplThread) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, OverscrollOnImplThread) { InputHandlerScrollResult scroll_result; LayerTreeSettings settings = DefaultSettings(); CreateHostImpl(settings, CreateLayerTreeFrameSink()); @@ -9514,7 +9790,7 @@ class BlendStateCheckLayer : public LayerImpl { viz::ResourceId resource_id_; }; -TEST_F(LayerTreeHostImplTest, BlendingOffWhenDrawingOpaqueLayers) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, BlendingOffWhenDrawingOpaqueLayers) { LayerImpl* root = SetupDefaultRootLayer(gfx::Size(10, 10)); root->SetDrawsContent(false); @@ -9710,7 +9986,7 @@ static bool MayContainVideoBitSetOnFrameData(LayerTreeHostImpl* host_impl) { return frame.may_contain_video; } -TEST_F(LayerTreeHostImplTest, MayContainVideo) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, MayContainVideo) { gfx::Size big_size(1000, 1000); auto* root = SetupRootLayer<DidDrawCheckLayer>(host_impl_->active_tree(), big_size); @@ -10020,7 +10296,7 @@ class FakeDrawableLayerImpl : public LayerImpl { // Make sure damage tracking propagates all the way to the viz::CompositorFrame // submitted to the LayerTreeFrameSink, where it should request to swap only // the sub-buffer that is damaged. -TEST_F(LayerTreeHostImplTest, PartialSwapReceivesDamageRect) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, PartialSwapReceivesDamageRect) { auto gl_owned = std::make_unique<viz::TestGLES2Interface>(); gl_owned->set_have_post_sub_buffer(true); scoped_refptr<viz::TestContextProvider> context_provider( @@ -10108,7 +10384,7 @@ TEST_F(LayerTreeHostImplTest, PartialSwapReceivesDamageRect) { layer_tree_host_impl->ReleaseLayerTreeFrameSink(); } -TEST_F(LayerTreeHostImplTest, RootLayerDoesntCreateExtraSurface) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, RootLayerDoesntCreateExtraSurface) { LayerImpl* root = SetupDefaultRootLayer(gfx::Size(10, 10)); LayerImpl* child = AddLayer(); child->SetBounds(gfx::Size(10, 10)); @@ -10153,7 +10429,7 @@ class FakeLayerWithQuads : public LayerImpl { : LayerImpl(tree_impl, id) {} }; -TEST_F(LayerTreeHostImplTest, LayersFreeTextures) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, LayersFreeTextures) { scoped_refptr<viz::TestContextProvider> context_provider = viz::TestContextProvider::Create(); viz::TestSharedImageInterface* sii = context_provider->SharedImageInterface(); @@ -10187,7 +10463,7 @@ TEST_F(LayerTreeHostImplTest, LayersFreeTextures) { EXPECT_EQ(0u, sii->shared_image_count()); } -TEST_F(LayerTreeHostImplTest, HasTransparentBackground) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, HasTransparentBackground) { SetupDefaultRootLayer(gfx::Size(10, 10)); host_impl_->active_tree()->set_background_color(SK_ColorWHITE); UpdateDrawProperties(host_impl_->active_tree()); @@ -10332,7 +10608,7 @@ class GLRendererWithSetupQuadForAntialiasing : public viz::GLRenderer { using viz::GLRenderer::ShouldAntialiasQuad; }; -TEST_F(LayerTreeHostImplTest, FarAwayQuadsDontNeedAA) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, FarAwayQuadsDontNeedAA) { // Due to precision issues (especially on Android), sometimes far // away quads can end up thinking they need AA. float device_scale_factor = 4 / 3; @@ -10433,7 +10709,7 @@ class CountingSoftwareDevice : public viz::SoftwareOutputDevice { int frames_began_, frames_ended_; }; -TEST_F(LayerTreeHostImplTest, +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ForcedDrawToSoftwareDeviceSkipsUnsupportedLayers) { set_reduce_memory_result(false); EXPECT_TRUE(CreateHostImpl(DefaultSettings(), @@ -10468,7 +10744,7 @@ TEST_F(LayerTreeHostImplTest, } // Checks that we use the memory limits provided. -TEST_F(LayerTreeHostImplTest, MemoryLimits) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, MemoryLimits) { host_impl_->ReleaseLayerTreeFrameSink(); host_impl_ = nullptr; @@ -10628,7 +10904,7 @@ TEST_F(LayerTreeHostImplTestPrepareTiles, PrepareTilesWhenInvisible) { EXPECT_TRUE(fake_host_impl_->prepare_tiles_needed()); } -TEST_F(LayerTreeHostImplTest, UIResourceManagement) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, UIResourceManagement) { auto test_context_provider = viz::TestContextProvider::Create(); viz::TestSharedImageInterface* sii = test_context_provider->SharedImageInterface(); @@ -10671,7 +10947,7 @@ TEST_F(LayerTreeHostImplTest, UIResourceManagement) { EXPECT_EQ(0u, sii->shared_image_count()); } -TEST_F(LayerTreeHostImplTest, CreateETC1UIResource) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, CreateETC1UIResource) { auto test_context_provider = viz::TestContextProvider::Create(); viz::TestSharedImageInterface* sii = test_context_provider->SharedImageInterface(); @@ -10696,7 +10972,7 @@ TEST_F(LayerTreeHostImplTest, CreateETC1UIResource) { EXPECT_NE(0u, id1); } -TEST_F(LayerTreeHostImplTest, ObeyMSAACaps) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ObeyMSAACaps) { LayerTreeSettings msaaSettings = DefaultSettings(); msaaSettings.gpu_rasterization_msaa_sample_count = 4; @@ -10806,22 +11082,27 @@ TEST_P(LayerTreeHostImplTestWithRenderer, ShutdownReleasesContext) { LayerImpl* root = SetupDefaultRootLayer(gfx::Size(10, 10)); struct Helper { std::unique_ptr<viz::CopyOutputResult> unprocessed_result; - void OnResult(std::unique_ptr<viz::CopyOutputResult> result) { + void OnResult(base::OnceClosure finished, + std::unique_ptr<viz::CopyOutputResult> result) { unprocessed_result = std::move(result); + std::move(finished).Run(); } } helper; GetEffectNode(root)->has_copy_request = true; + base::RunLoop copy_request_run_loop; GetPropertyTrees(root)->effect_tree.AddCopyRequest( root->effect_tree_index(), std::make_unique<viz::CopyOutputRequest>( viz::CopyOutputRequest::ResultFormat::RGBA_TEXTURE, - base::BindOnce(&Helper::OnResult, base::Unretained(&helper)))); + base::BindOnce(&Helper::OnResult, base::Unretained(&helper), + copy_request_run_loop.QuitClosure()))); DrawFrame(); auto* sii = context_provider->SharedImageInterface(); // The CopyOutputResult has a ref on the viz::ContextProvider and a shared // image allocated. + copy_request_run_loop.Run(); EXPECT_TRUE(helper.unprocessed_result); EXPECT_FALSE(context_provider->HasOneRef()); EXPECT_EQ(1u, sii->shared_image_count()); @@ -10839,12 +11120,15 @@ TEST_P(LayerTreeHostImplTestWithRenderer, ShutdownReleasesContext) { helper.unprocessed_result.reset(); } -TEST_F(LayerTreeHostImplTest, ScrollUnknownNotOnAncestorChain) { +// This tests the case where hit testing only on scrollable layers returns a +// layer that's outside the scroll chain of the first hit test *any* layer. See +// LayerTreeHostImpl::IsInitialScrollHitTestReliable for details. +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollHitTestIsNotReliable) { // If we ray cast a scroller that is not on the first layer's ancestor chain, // we should return SCROLL_UNKNOWN. gfx::Size viewport_size(50, 50); gfx::Size content_size(100, 100); - SetupViewportLayersInnerScrolls(viewport_size, content_size); + SetupViewportLayersOuterScrolls(viewport_size, content_size); LayerImpl* occluder_layer = AddLayer(); occluder_layer->SetDrawsContent(true); @@ -10863,20 +11147,27 @@ TEST_F(LayerTreeHostImplTest, ScrollUnknownNotOnAncestorChain) { ui::ScrollInputType::kWheel) .get(), ui::ScrollInputType::kWheel); - EXPECT_EQ(InputHandler::SCROLL_UNKNOWN, status.thread); - EXPECT_EQ(MainThreadScrollingReason::kFailedHitTest, - status.main_thread_scrolling_reasons); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_TRUE(status.needs_main_thread_hit_test); + } else { + EXPECT_EQ(InputHandler::SCROLL_UNKNOWN, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kFailedHitTest, + status.main_thread_scrolling_reasons); + } } -TEST_F(LayerTreeHostImplTest, ScrollUnknownScrollAncestorMismatch) { +// Similar but different case to above. See +// LayerTreeHostImpl::IsInitialScrollHitTestReliable for details. +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollHitTestAncestorMismatch) { // If we ray cast a scroller this is on the first layer's ancestor chain, but // is not the first scroller we encounter when walking up from the layer, we // should also return SCROLL_UNKNOWN. gfx::Size viewport_size(50, 50); gfx::Size content_size(100, 100); - SetupViewportLayersInnerScrolls(viewport_size, content_size); + SetupViewportLayersOuterScrolls(viewport_size, content_size); - LayerImpl* scroll_layer = InnerViewportScrollLayer(); + LayerImpl* scroll_layer = OuterViewportScrollLayer(); LayerImpl* child_scroll_clip = AddLayer(); CopyProperties(scroll_layer, child_scroll_clip); @@ -10899,12 +11190,17 @@ TEST_F(LayerTreeHostImplTest, ScrollUnknownScrollAncestorMismatch) { ui::ScrollInputType::kWheel) .get(), ui::ScrollInputType::kWheel); - EXPECT_EQ(InputHandler::SCROLL_UNKNOWN, status.thread); - EXPECT_EQ(MainThreadScrollingReason::kFailedHitTest, - status.main_thread_scrolling_reasons); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_TRUE(status.needs_main_thread_hit_test); + } else { + EXPECT_EQ(InputHandler::SCROLL_UNKNOWN, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kFailedHitTest, + status.main_thread_scrolling_reasons); + } } -TEST_F(LayerTreeHostImplTest, ScrollInvisibleScroller) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollInvisibleScroller) { gfx::Size viewport_size(50, 50); gfx::Size content_size(100, 100); SetupViewportLayersInnerScrolls(viewport_size, content_size); @@ -10932,7 +11228,8 @@ TEST_F(LayerTreeHostImplTest, ScrollInvisibleScroller) { // Make sure LatencyInfo carried by LatencyInfoSwapPromise are passed // in viz::CompositorFrameMetadata. -TEST_F(LayerTreeHostImplTest, LatencyInfoPassedToCompositorFrameMetadata) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + LatencyInfoPassedToCompositorFrameMetadata) { LayerTreeSettings settings = DefaultSettings(); settings.commit_to_active_tree = false; CreateHostImpl(settings, CreateLayerTreeFrameSink()); @@ -10962,7 +11259,8 @@ TEST_F(LayerTreeHostImplTest, LatencyInfoPassedToCompositorFrameMetadata) { } #if defined(OS_ANDROID) -TEST_F(LayerTreeHostImplTest, SelectionBoundsPassedToCompositorFrameMetadata) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + SelectionBoundsPassedToCompositorFrameMetadata) { LayerImpl* root = SetupRootLayer<SolidColorLayerImpl>( host_impl_->active_tree(), gfx::Size(10, 10)); UpdateDrawProperties(host_impl_->active_tree()); @@ -10992,7 +11290,7 @@ TEST_F(LayerTreeHostImplTest, SelectionBoundsPassedToCompositorFrameMetadata) { EXPECT_TRUE(selection_after.end.visible()); } -TEST_F(LayerTreeHostImplTest, HiddenSelectionBoundsStayHidden) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, HiddenSelectionBoundsStayHidden) { LayerImpl* root = SetupDefaultRootLayer(gfx::Size(10, 10)); UpdateDrawProperties(host_impl_->active_tree()); @@ -11051,7 +11349,7 @@ class SimpleSwapPromiseMonitor : public SwapPromiseMonitor { int* set_needs_redraw_count_; }; -TEST_F(LayerTreeHostImplTest, SimpleSwapPromiseMonitor) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, SimpleSwapPromiseMonitor) { int set_needs_commit_count = 0; int set_needs_redraw_count = 0; @@ -11622,7 +11920,7 @@ TEST_F(LayerTreeHostImplWithBrowserControlsTest, // Tests that when we set a child scroller (e.g. a scrolling div) as the outer // viewport, scrolling it controls the browser controls. -TEST_F(LayerTreeHostImplBrowserControlsTest, +TEST_P(LayerTreeHostImplBrowserControlsTest, ReplacedOuterViewportScrollsBrowserControls) { const gfx::Size scroll_content_size(400, 400); const gfx::Size root_layer_size(200, 200); @@ -11859,7 +12157,8 @@ TEST_F(LayerTreeHostImplWithImplicitLimitsTest, ImplicitMemoryLimits) { 150u * 1024u * 1024u); } -TEST_F(LayerTreeHostImplTest, ExternalTransformReflectedInNextDraw) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ExternalTransformReflectedInNextDraw) { const gfx::Size viewport_size(50, 50); const gfx::Size layer_size(100, 100); gfx::Transform external_transform; @@ -11885,7 +12184,7 @@ TEST_F(LayerTreeHostImplTest, ExternalTransformReflectedInNextDraw) { external_transform, layer->draw_properties().target_space_transform); } -TEST_F(LayerTreeHostImplTest, ExternalTransformSetNeedsRedraw) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ExternalTransformSetNeedsRedraw) { const gfx::Size viewport_size(100, 100); SetupDefaultRootLayer(viewport_size); UpdateDrawProperties(host_impl_->active_tree()); @@ -11914,7 +12213,7 @@ TEST_F(LayerTreeHostImplTest, ExternalTransformSetNeedsRedraw) { EXPECT_FALSE(last_on_draw_frame_->has_no_damage); } -TEST_F(LayerTreeHostImplTest, OnMemoryPressure) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, OnMemoryPressure) { gfx::Size size(200, 200); viz::ResourceFormat format = viz::RGBA_8888; gfx::ColorSpace color_space = gfx::ColorSpace::CreateSRGB(); @@ -11936,7 +12235,7 @@ TEST_F(LayerTreeHostImplTest, OnMemoryPressure) { EXPECT_LT(memory_usage_after_memory_pressure, current_memory_usage); } -TEST_F(LayerTreeHostImplTest, OnDrawConstraintSetNeedsRedraw) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, OnDrawConstraintSetNeedsRedraw) { const gfx::Size viewport_size(100, 100); SetupDefaultRootLayer(viewport_size); UpdateDrawProperties(host_impl_->active_tree()); @@ -11969,7 +12268,7 @@ TEST_F(LayerTreeHostImplTest, OnDrawConstraintSetNeedsRedraw) { // This test verifies that the viewport damage rect is the full viewport and not // just part of the viewport in the presence of an external viewport. -TEST_F(LayerTreeHostImplTest, FullViewportDamageAfterOnDraw) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, FullViewportDamageAfterOnDraw) { const gfx::Size viewport_size(100, 100); SetupDefaultRootLayer(viewport_size); UpdateDrawProperties(host_impl_->active_tree()); @@ -12089,7 +12388,8 @@ TEST_F(CommitToPendingTreeLayerTreeHostImplTest, EXPECT_TRUE(host_impl_->active_tree()->needs_update_draw_properties()); } -TEST_F(LayerTreeHostImplTest, ExternalViewportAffectsVisibleRects) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ExternalViewportAffectsVisibleRects) { const gfx::Size viewport_size(50, 50); const gfx::Size layer_size(100, 100); SetupViewportLayersInnerScrolls(viewport_size, layer_size); @@ -12118,7 +12418,8 @@ TEST_F(LayerTreeHostImplTest, ExternalViewportAffectsVisibleRects) { EXPECT_EQ(gfx::Rect(90, 90), content_layer->visible_layer_rect()); } -TEST_F(LayerTreeHostImplTest, ExternalTransformAffectsVisibleRects) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ExternalTransformAffectsVisibleRects) { const gfx::Size viewport_size(50, 50); const gfx::Size layer_size(100, 100); SetupViewportLayersInnerScrolls(viewport_size, layer_size); @@ -12152,7 +12453,8 @@ TEST_F(LayerTreeHostImplTest, ExternalTransformAffectsVisibleRects) { EXPECT_EQ(gfx::Rect(50, 50), content_layer->visible_layer_rect()); } -TEST_F(LayerTreeHostImplTest, ExternalTransformAffectsSublayerScaleFactor) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ExternalTransformAffectsSublayerScaleFactor) { const gfx::Size viewport_size(50, 50); const gfx::Size layer_size(100, 100); SetupViewportLayersInnerScrolls(viewport_size, layer_size); @@ -12196,7 +12498,7 @@ TEST_F(LayerTreeHostImplTest, ExternalTransformAffectsSublayerScaleFactor) { #if defined(OS_MACOSX) // Ensure Mac wheel scrolling causes instant scrolling. This test can be removed // once https://crbug.com/574283 is fixed. -TEST_F(LayerTreeHostImplTest, MacWheelIsNonAnimated) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, MacWheelIsNonAnimated) { const gfx::Size content_size(1000, 1000); const gfx::Size viewport_size(50, 100); SetupViewportLayersOuterScrolls(viewport_size, content_size); @@ -12219,7 +12521,116 @@ TEST_F(LayerTreeHostImplTest, MacWheelIsNonAnimated) { } #endif -TEST_F(LayerTreeHostImplTest, ScrollAnimated) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, OneScrollForFirstScrollDelay) { + LayerTreeSettings settings = DefaultSettings(); + settings.commit_to_active_tree = false; + CreateHostImpl(settings, CreateLayerTreeFrameSink()); + SetupRootLayer<SolidColorLayerImpl>(host_impl_->active_tree(), + gfx::Size(10, 10)); + UpdateDrawProperties(host_impl_->active_tree()); + EXPECT_EQ(first_scroll_observed, 0); + + // LatencyInfo for the first scroll. + ui::LatencyInfo latency_info; + latency_info.set_trace_id(5); + latency_info.AddLatencyNumber( + ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT); + std::unique_ptr<SwapPromise> swap_promise( + new LatencyInfoSwapPromise(latency_info)); + host_impl_->active_tree()->QueuePinnedSwapPromise(std::move(swap_promise)); + + host_impl_->SetFullViewportDamage(); + host_impl_->SetNeedsRedraw(); + DrawFrame(); + + constexpr uint32_t frame_token_1 = 1; + viz::FrameTimingDetails mock_details; + mock_details.presentation_feedback = ExampleFeedback(); + // When the LayerTreeHostImpl receives presentation feedback, the callback + // will be fired. + host_impl_->DidPresentCompositorFrame(frame_token_1, mock_details); + + EXPECT_EQ(first_scroll_observed, 1); +} + +TEST_P(ScrollUnifiedLayerTreeHostImplTest, OtherInputsForFirstScrollDelay) { + LayerTreeSettings settings = DefaultSettings(); + settings.commit_to_active_tree = false; + CreateHostImpl(settings, CreateLayerTreeFrameSink()); + SetupRootLayer<SolidColorLayerImpl>(host_impl_->active_tree(), + gfx::Size(10, 10)); + UpdateDrawProperties(host_impl_->active_tree()); + EXPECT_EQ(first_scroll_observed, 0); + + // LatencyInfo for the first input, which is not scroll. + ui::LatencyInfo latency_info; + latency_info.set_trace_id(5); + latency_info.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT); + std::unique_ptr<SwapPromise> swap_promise( + new LatencyInfoSwapPromise(latency_info)); + host_impl_->active_tree()->QueuePinnedSwapPromise(std::move(swap_promise)); + + host_impl_->SetFullViewportDamage(); + host_impl_->SetNeedsRedraw(); + DrawFrame(); + + constexpr uint32_t frame_token_1 = 1; + viz::FrameTimingDetails mock_details; + mock_details.presentation_feedback = ExampleFeedback(); + // When the LayerTreeHostImpl receives presentation feedback, the callback + // will be fired. + host_impl_->DidPresentCompositorFrame(frame_token_1, mock_details); + + EXPECT_EQ(first_scroll_observed, 0); +} + +TEST_P(ScrollUnifiedLayerTreeHostImplTest, MultipleScrollsForFirstScrollDelay) { + LayerTreeSettings settings = DefaultSettings(); + settings.commit_to_active_tree = false; + CreateHostImpl(settings, CreateLayerTreeFrameSink()); + SetupRootLayer<SolidColorLayerImpl>(host_impl_->active_tree(), + gfx::Size(10, 10)); + UpdateDrawProperties(host_impl_->active_tree()); + EXPECT_EQ(first_scroll_observed, 0); + + // LatencyInfo for the first scroll. + ui::LatencyInfo latency_info; + latency_info.set_trace_id(5); + latency_info.AddLatencyNumber( + ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT); + std::unique_ptr<SwapPromise> swap_promise( + new LatencyInfoSwapPromise(latency_info)); + host_impl_->active_tree()->QueuePinnedSwapPromise(std::move(swap_promise)); + DrawFrame(); + constexpr uint32_t frame_token_1 = 1; + viz::FrameTimingDetails mock_details; + mock_details.presentation_feedback = ExampleFeedback(); + // When the LayerTreeHostImpl receives presentation feedback, the callback + // will be fired. + host_impl_->DidPresentCompositorFrame(frame_token_1, mock_details); + EXPECT_EQ(first_scroll_observed, 1); + + // LatencyInfo for the second scroll. + ui::LatencyInfo latency_info2; + latency_info2.set_trace_id(6); + latency_info2.AddLatencyNumber( + ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT); + std::unique_ptr<SwapPromise> swap_promise2( + new LatencyInfoSwapPromise(latency_info2)); + host_impl_->active_tree()->QueuePinnedSwapPromise(std::move(swap_promise2)); + host_impl_->SetFullViewportDamage(); + host_impl_->SetNeedsRedraw(); + DrawFrame(); + constexpr uint32_t frame_token_2 = 2; + viz::FrameTimingDetails mock_details2; + mock_details2.presentation_feedback = ExampleFeedback(); + // When the LayerTreeHostImpl receives presentation feedback, the callback + // will be fired. + host_impl_->DidPresentCompositorFrame(frame_token_2, mock_details2); + EXPECT_EQ(first_scroll_observed, 1); +} + +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollAnimated) { const gfx::Size content_size(1000, 1000); const gfx::Size viewport_size(50, 100); SetupViewportLayersOuterScrolls(viewport_size, content_size); @@ -12327,7 +12738,7 @@ TEST_F(LayerTreeHostImplTest, ScrollAnimated) { // Tests latching behavior, in particular when a ScrollEnd is received but a // new ScrollBegin is received before the animation from the previous gesture // stream is finished. -TEST_F(LayerTreeHostImplTest, ScrollAnimatedLatching) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollAnimatedLatching) { const gfx::Size content_size(1000, 1000); const gfx::Size viewport_size(50, 100); SetupViewportLayersOuterScrolls(viewport_size, content_size); @@ -12426,7 +12837,7 @@ TEST_F(LayerTreeHostImplTest, ScrollAnimatedLatching) { // factor. That is, if you zoom into the page, a wheel scroll should scroll the // content *less* than before so that it appears to move the same distance when // zoomed in. -TEST_F(LayerTreeHostImplTest, ScrollAnimatedWhileZoomed) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollAnimatedWhileZoomed) { const gfx::Size content_size(1000, 1000); const gfx::Size viewport_size(50, 100); SetupViewportLayersOuterScrolls(viewport_size, content_size); @@ -12503,7 +12914,7 @@ TEST_F(LayerTreeHostImplTest, ScrollAnimatedWhileZoomed) { // inner viewport is animating. Specifically this test makes sure the animation // update doesn't get confused between the currently scrolling node and the // currently animating node which are different. See https://crbug.com/1070561. -TEST_F(LayerTreeHostImplTest, ScrollAnimatedUpdateInnerViewport) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollAnimatedUpdateInnerViewport) { const gfx::Size content_size(210, 1000); const gfx::Size viewport_size(200, 200); SetupViewportLayersOuterScrolls(viewport_size, content_size); @@ -12602,7 +13013,8 @@ TEST_F(LayerTreeHostImplTest, ScrollAnimatedUpdateInnerViewport) { } // This tests that faded-out Aura scrollbars can't be interacted with. -TEST_F(LayerTreeHostImplTest, FadedOutPaintedOverlayScrollbarHitTest) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + FadedOutPaintedOverlayScrollbarHitTest) { LayerTreeSettings settings = DefaultSettings(); settings.compositor_threaded_scrollbar_scrolling = true; CreateHostImpl(settings, CreateLayerTreeFrameSink()); @@ -12645,7 +13057,7 @@ TEST_F(LayerTreeHostImplTest, FadedOutPaintedOverlayScrollbarHitTest) { auto& scrollbar_effect_node = CreateEffectNode(scrollbar); scrollbar_effect_node.opacity = 0.8; - host_impl_->MouseDown(gfx::PointF(350, 18), /*shift_modifier*/ false); + host_impl_->MouseDown(gfx::PointF(350, 18), /*jump_key_modifier*/ false); InputHandlerPointerResult result = host_impl_->MouseMoveAt(gfx::Point(350, 28)); EXPECT_GT(result.scroll_offset.y(), 0u); @@ -12654,7 +13066,7 @@ TEST_F(LayerTreeHostImplTest, FadedOutPaintedOverlayScrollbarHitTest) { // Scrolling shouldn't occur at opacity = 0. scrollbar_effect_node.opacity = 0; - host_impl_->MouseDown(gfx::PointF(350, 18), /*shift_modifier*/ false); + host_impl_->MouseDown(gfx::PointF(350, 18), /*jump_key_modifier*/ false); result = host_impl_->MouseMoveAt(gfx::Point(350, 28)); EXPECT_EQ(result.scroll_offset.y(), 0u); host_impl_->MouseUp(gfx::PointF(350, 28)); @@ -12664,11 +13076,73 @@ TEST_F(LayerTreeHostImplTest, FadedOutPaintedOverlayScrollbarHitTest) { host_impl_ = nullptr; } +// Tests that deleting a horizontal scrollbar doesn't affect the autoscroll task +// for the vertical scrollbar. +TEST_P(ScrollUnifiedLayerTreeHostImplTest, AutoscrollOnDeletedScrollbar) { + LayerTreeSettings settings = DefaultSettings(); + settings.compositor_threaded_scrollbar_scrolling = true; + CreateHostImpl(settings, CreateLayerTreeFrameSink()); + + // Setup the viewport. + const gfx::Size viewport_size = gfx::Size(360, 600); + const gfx::Size content_size = gfx::Size(345, 3800); + SetupViewportLayersOuterScrolls(viewport_size, content_size); + LayerImpl* scroll_layer = OuterViewportScrollLayer(); + + // Set up the scrollbar and its dimensions. + LayerTreeImpl* layer_tree_impl = host_impl_->active_tree(); + auto* scrollbar = AddLayer<PaintedScrollbarLayerImpl>( + layer_tree_impl, ScrollbarOrientation::VERTICAL, false, true); + SetupScrollbarLayerCommon(scroll_layer, scrollbar); + scrollbar->SetHitTestable(true); + + const gfx::Size scrollbar_size = gfx::Size(15, 600); + scrollbar->SetBounds(scrollbar_size); + + // Set up the thumb dimensions. + scrollbar->SetThumbThickness(15); + scrollbar->SetThumbLength(50); + scrollbar->SetTrackRect(gfx::Rect(0, 15, 15, 575)); + scrollbar->SetOffsetToTransformParent(gfx::Vector2dF(345, 0)); + + TestInputHandlerClient input_handler_client; + host_impl_->BindToClient(&input_handler_client); + + // PointerDown on the scrollbar schedules an autoscroll task. + host_impl_->MouseDown(gfx::PointF(350, 580), /*jump_key_modifier*/ false); + EXPECT_TRUE(!host_impl_->CurrentlyScrollingNode()); + + // Now, unregister the horizontal scrollbar and test that the autoscroll task + // that was scheduled for the vertical scrollbar still exists. (Note that + // adding a horizontal scrollbar layer is not needed. This test merely checks + // the logic inside ScrollbarController::DidUnregisterScrollbar) + host_impl_->DidUnregisterScrollbarLayer(scroll_layer->element_id(), + ScrollbarOrientation::HORIZONTAL); + EXPECT_TRUE(host_impl_->scrollbar_controller_for_testing() + ->AutoscrollTaskIsScheduled()); + + // If a call comes in to delete the scrollbar layer for which the autoscroll + // was scheduled, the autoscroll task should be cancelled. + host_impl_->DidUnregisterScrollbarLayer(scroll_layer->element_id(), + ScrollbarOrientation::VERTICAL); + EXPECT_FALSE(host_impl_->scrollbar_controller_for_testing() + ->AutoscrollTaskIsScheduled()); + + // End the scroll. + host_impl_->MouseUp(gfx::PointF(350, 580)); + host_impl_->ScrollEnd(); + EXPECT_TRUE(!host_impl_->CurrentlyScrollingNode()); + + // Tear down the LayerTreeHostImpl before the InputHandlerClient. + host_impl_->ReleaseLayerTreeFrameSink(); + host_impl_ = nullptr; +} + // Tests that a pointerdown followed by pointermove(s) produces // InputHandlerPointerResult with scroll_offset > 0 even though the GSB might // have been dispatched *after* the first pointermove was handled by the // ScrollbarController. -TEST_F(LayerTreeHostImplTest, PointerMoveOutOfSequence) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, PointerMoveOutOfSequence) { LayerTreeSettings settings = DefaultSettings(); settings.compositor_threaded_scrollbar_scrolling = true; CreateHostImpl(settings, CreateLayerTreeFrameSink()); @@ -12701,7 +13175,7 @@ TEST_F(LayerTreeHostImplTest, PointerMoveOutOfSequence) { // PointerDown sets up the state needed to initiate a drag. Although, the // resulting GSB won't be dispatched until the next VSync. Hence, the // CurrentlyScrollingNode is expected to be null. - host_impl_->MouseDown(gfx::PointF(350, 18), /*shift_modifier*/ false); + host_impl_->MouseDown(gfx::PointF(350, 18), /*jump_key_modifier*/ false); EXPECT_TRUE(!host_impl_->CurrentlyScrollingNode()); // PointerMove arrives before the next VSync. This still needs to be handled @@ -12740,7 +13214,7 @@ TEST_F(LayerTreeHostImplTest, PointerMoveOutOfSequence) { } // This tests that faded-out Mac scrollbars can't be interacted with. -TEST_F(LayerTreeHostImplTest, FadedOutPaintedScrollbarHitTest) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, FadedOutPaintedScrollbarHitTest) { LayerTreeSettings settings = DefaultSettings(); settings.compositor_threaded_scrollbar_scrolling = true; CreateHostImpl(settings, CreateLayerTreeFrameSink()); @@ -12774,14 +13248,14 @@ TEST_F(LayerTreeHostImplTest, FadedOutPaintedScrollbarHitTest) { // scroll. scrollbar->set_scrollbar_painted_opacity(0); InputHandlerPointerResult result = - host_impl_->MouseDown(gfx::PointF(350, 100), /*shift_modifier*/ false); + host_impl_->MouseDown(gfx::PointF(350, 100), /*jump_key_modifier*/ false); EXPECT_EQ(result.scroll_offset.y(), 0u); // MouseDown on the track of a scrollbar with opacity > 0 should produce a // scroll. scrollbar->set_scrollbar_painted_opacity(1); result = - host_impl_->MouseDown(gfx::PointF(350, 100), /*shift_modifier*/ false); + host_impl_->MouseDown(gfx::PointF(350, 100), /*jump_key_modifier*/ false); EXPECT_GT(result.scroll_offset.y(), 0u); // Tear down the LayerTreeHostImpl before the InputHandlerClient. @@ -12789,7 +13263,8 @@ TEST_F(LayerTreeHostImplTest, FadedOutPaintedScrollbarHitTest) { host_impl_ = nullptr; } -TEST_F(LayerTreeHostImplTest, SingleGSUForScrollbarThumbDragPerFrame) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + SingleGSUForScrollbarThumbDragPerFrame) { LayerTreeSettings settings = DefaultSettings(); settings.compositor_threaded_scrollbar_scrolling = true; CreateHostImpl(settings, CreateLayerTreeFrameSink()); @@ -12839,7 +13314,7 @@ TEST_F(LayerTreeHostImplTest, SingleGSUForScrollbarThumbDragPerFrame) { // MouseDown on the thumb should not produce a scroll. InputHandlerPointerResult result = - host_impl_->MouseDown(gfx::PointF(350, 18), /*shift_modifier*/ false); + host_impl_->MouseDown(gfx::PointF(350, 18), /*jump_key_modifier*/ false); EXPECT_EQ(result.scroll_offset.y(), 0u); // The first request for a GSU should be processed as expected. @@ -12875,9 +13350,176 @@ TEST_F(LayerTreeHostImplTest, SingleGSUForScrollbarThumbDragPerFrame) { host_impl_ = nullptr; } +// Tests that the scheduled autoscroll task aborts if a 2nd mousedown occurs in +// the same frame. +TEST_F(LayerTreeHostImplTest, AutoscrollTaskAbort) { + LayerTreeSettings settings = DefaultSettings(); + settings.compositor_threaded_scrollbar_scrolling = true; + CreateHostImpl(settings, CreateLayerTreeFrameSink()); + + // Setup the viewport. + const gfx::Size viewport_size = gfx::Size(360, 600); + const gfx::Size content_size = gfx::Size(345, 3800); + SetupViewportLayersOuterScrolls(viewport_size, content_size); + LayerImpl* scroll_layer = OuterViewportScrollLayer(); + + // Set up the scrollbar and its dimensions. + LayerTreeImpl* layer_tree_impl = host_impl_->active_tree(); + auto* scrollbar = AddLayer<PaintedScrollbarLayerImpl>( + layer_tree_impl, VERTICAL, /*is_left_side_vertical_scrollbar*/ false, + /*is_overlay*/ false); + + SetupScrollbarLayer(scroll_layer, scrollbar); + const gfx::Size scrollbar_size = gfx::Size(15, 600); + scrollbar->SetBounds(scrollbar_size); + host_impl_->set_force_smooth_wheel_scrolling_for_testing(true); + + // Set up the thumb dimensions. + scrollbar->SetThumbThickness(15); + scrollbar->SetThumbLength(50); + scrollbar->SetTrackRect(gfx::Rect(0, 15, 15, 575)); + + // Set up scrollbar arrows. + scrollbar->SetBackButtonRect( + gfx::Rect(gfx::Point(345, 0), gfx::Size(15, 15))); + scrollbar->SetForwardButtonRect( + gfx::Rect(gfx::Point(345, 570), gfx::Size(15, 15))); + scrollbar->SetOffsetToTransformParent(gfx::Vector2dF(345, 0)); + + TestInputHandlerClient input_handler_client; + host_impl_->BindToClient(&input_handler_client); + + { + // An autoscroll task gets scheduled on mousedown. + InputHandlerPointerResult result = host_impl_->MouseDown( + gfx::PointF(350, 575), /*jump_key_modifier*/ false); + EXPECT_EQ(result.type, PointerResultType::kScrollbarScroll); + auto begin_state = BeginState(gfx::Point(350, 575), gfx::Vector2d(0, 40), + ui::ScrollInputType::kScrollbar); + EXPECT_EQ( + InputHandler::SCROLL_ON_IMPL_THREAD, + host_impl_ + ->ScrollBegin(begin_state.get(), ui::ScrollInputType::kScrollbar) + .thread); + EXPECT_TRUE(host_impl_->scrollbar_controller_for_testing() + ->AutoscrollTaskIsScheduled()); + } + + { + // Another mousedown occurs in the same frame. InputHandlerProxy calls + // LayerTreeHostImpl::ScrollEnd and the autoscroll task should be cancelled. + InputHandlerPointerResult result = host_impl_->MouseDown( + gfx::PointF(350, 575), /*jump_key_modifier*/ false); + EXPECT_EQ(result.type, PointerResultType::kScrollbarScroll); + host_impl_->ScrollEnd(); + EXPECT_FALSE(host_impl_->scrollbar_controller_for_testing() + ->AutoscrollTaskIsScheduled()); + } + + // Tear down the LayerTreeHostImpl before the InputHandlerClient. + host_impl_->ReleaseLayerTreeFrameSink(); + host_impl_ = nullptr; +} + +// Tests that the ScrollbarController handles jump clicks. +TEST_F(LayerTreeHostImplTest, JumpOnScrollbarClick) { + LayerTreeSettings settings = DefaultSettings(); + settings.compositor_threaded_scrollbar_scrolling = true; + CreateHostImpl(settings, CreateLayerTreeFrameSink()); + + // Setup the viewport. + const gfx::Size viewport_size = gfx::Size(360, 600); + const gfx::Size content_size = gfx::Size(345, 3800); + SetupViewportLayersOuterScrolls(viewport_size, content_size); + LayerImpl* scroll_layer = OuterViewportScrollLayer(); + + // Set up the scrollbar and its dimensions. + LayerTreeImpl* layer_tree_impl = host_impl_->active_tree(); + auto* scrollbar = AddLayer<PaintedScrollbarLayerImpl>( + layer_tree_impl, VERTICAL, /*is_left_side_vertical_scrollbar*/ false, + /*is_overlay*/ false); + + SetupScrollbarLayer(scroll_layer, scrollbar); + const gfx::Size scrollbar_size = gfx::Size(15, 600); + scrollbar->SetBounds(scrollbar_size); + host_impl_->set_force_smooth_wheel_scrolling_for_testing(true); + + // Set up the thumb dimensions. + scrollbar->SetThumbThickness(15); + scrollbar->SetThumbLength(50); + scrollbar->SetTrackRect(gfx::Rect(0, 15, 15, 575)); + + // Set up scrollbar arrows. + scrollbar->SetBackButtonRect( + gfx::Rect(gfx::Point(345, 0), gfx::Size(15, 15))); + scrollbar->SetForwardButtonRect( + gfx::Rect(gfx::Point(345, 570), gfx::Size(15, 15))); + scrollbar->SetOffsetToTransformParent(gfx::Vector2dF(345, 0)); + + TestInputHandlerClient input_handler_client; + host_impl_->BindToClient(&input_handler_client); + + // Verify all 4 combinations of JumpOnTrackClick and jump_key_modifier. + { + // Click on track when JumpOnTrackClick is false and jump_key_modifier is + // false. Expected to perform a regular track scroll. + scrollbar->SetJumpOnTrackClick(false); + InputHandlerPointerResult result = host_impl_->MouseDown( + gfx::PointF(350, 400), /*jump_key_modifier*/ false); + EXPECT_EQ(result.type, PointerResultType::kScrollbarScroll); + EXPECT_EQ(result.scroll_offset.y(), 525); + result = host_impl_->MouseUp(gfx::PointF(350, 400)); + EXPECT_EQ(result.type, PointerResultType::kScrollbarScroll); + EXPECT_EQ(result.scroll_offset.y(), 0); + } + + { + // Click on track when JumpOnTrackClick is false and jump_key_modifier is + // true. Expected to perform scroller jump to the clicked location. + scrollbar->SetJumpOnTrackClick(false); + InputHandlerPointerResult result = host_impl_->MouseDown( + gfx::PointF(350, 400), /*jump_key_modifier*/ true); + EXPECT_EQ(result.type, PointerResultType::kScrollbarScroll); + EXPECT_EQ(result.scroll_offset.y(), 2194); + result = host_impl_->MouseUp(gfx::PointF(350, 400)); + EXPECT_EQ(result.type, PointerResultType::kScrollbarScroll); + EXPECT_EQ(result.scroll_offset.y(), 0); + } + + { + // Click on track when JumpOnTrackClick is true and jump_key_modifier is + // false. Expected to perform scroller jump to the clicked location. + scrollbar->SetJumpOnTrackClick(true); + InputHandlerPointerResult result = host_impl_->MouseDown( + gfx::PointF(350, 400), /*jump_key_modifier*/ false); + EXPECT_EQ(result.type, PointerResultType::kScrollbarScroll); + EXPECT_EQ(result.scroll_offset.y(), 2194); + result = host_impl_->MouseUp(gfx::PointF(350, 400)); + EXPECT_EQ(result.type, PointerResultType::kScrollbarScroll); + EXPECT_EQ(result.scroll_offset.y(), 0); + } + + { + // Click on track when JumpOnTrackClick is true and jump_key_modifier is + // true. Expected to perform a regular track scroll. + scrollbar->SetJumpOnTrackClick(true); + InputHandlerPointerResult result = host_impl_->MouseDown( + gfx::PointF(350, 400), /*jump_key_modifier*/ true); + EXPECT_EQ(result.type, PointerResultType::kScrollbarScroll); + EXPECT_EQ(result.scroll_offset.y(), 525); + result = host_impl_->MouseUp(gfx::PointF(350, 400)); + EXPECT_EQ(result.type, PointerResultType::kScrollbarScroll); + EXPECT_EQ(result.scroll_offset.y(), 0); + } + + // Tear down the LayerTreeHostImpl before the InputHandlerClient. + host_impl_->ReleaseLayerTreeFrameSink(); + host_impl_ = nullptr; +} + // Tests that an animated scrollbar scroll aborts when a different device (like // a mousewheel) wants to animate the scroll offset. -TEST_F(LayerTreeHostImplTest, AnimatedScrollYielding) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, AnimatedScrollYielding) { LayerTreeSettings settings = DefaultSettings(); settings.compositor_threaded_scrollbar_scrolling = true; CreateHostImpl(settings, CreateLayerTreeFrameSink()); @@ -12919,7 +13561,7 @@ TEST_F(LayerTreeHostImplTest, AnimatedScrollYielding) { { // Set up an animated scrollbar autoscroll. host_impl_->scrollbar_controller_for_testing()->HandlePointerDown( - gfx::PointF(350, 560), /*shift_modifier*/ false); + gfx::PointF(350, 560), /*jump_key_modifier*/ false); auto begin_state = BeginState(gfx::Point(350, 560), gfx::Vector2d(0, 40), ui::ScrollInputType::kScrollbar); EXPECT_EQ( @@ -12981,7 +13623,7 @@ TEST_F(LayerTreeHostImplTest, AnimatedScrollYielding) { // Tests that changing the scroller length in the middle of a thumb drag doesn't // cause the scroller to jump. -TEST_F(LayerTreeHostImplTest, ThumbDragScrollerLengthIncrease) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ThumbDragScrollerLengthIncrease) { LayerTreeSettings settings = DefaultSettings(); settings.compositor_threaded_scrollbar_scrolling = true; CreateHostImpl(settings, CreateLayerTreeFrameSink()); @@ -13028,7 +13670,7 @@ TEST_F(LayerTreeHostImplTest, ThumbDragScrollerLengthIncrease) { begin_frame_args.frame_id.sequence_number++; host_impl_->WillBeginImplFrame(begin_frame_args); InputHandlerPointerResult result = - host_impl_->MouseDown(gfx::PointF(350, 18), /*shift_modifier*/ false); + host_impl_->MouseDown(gfx::PointF(350, 18), /*jump_key_modifier*/ false); EXPECT_EQ(result.scroll_offset.y(), 0); EXPECT_EQ(result.type, PointerResultType::kScrollbarScroll); host_impl_->DidFinishImplFrame(begin_frame_args); @@ -13082,7 +13724,7 @@ TEST_F(LayerTreeHostImplTest, ThumbDragScrollerLengthIncrease) { host_impl_ = nullptr; } -TEST_F(LayerTreeHostImplTest, MainThreadFallback) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, MainThreadFallback) { LayerTreeSettings settings = DefaultSettings(); settings.compositor_threaded_scrollbar_scrolling = true; CreateHostImpl(settings, CreateLayerTreeFrameSink()); @@ -13118,7 +13760,7 @@ TEST_F(LayerTreeHostImplTest, MainThreadFallback) { // Clicking on the track should produce a scroll on the compositor thread. InputHandlerPointerResult compositor_threaded_scrolling_result = - host_impl_->MouseDown(gfx::PointF(350, 500), /*shift_modifier*/ false); + host_impl_->MouseDown(gfx::PointF(350, 500), /*jump_key_modifier*/ false); host_impl_->MouseUp(gfx::PointF(350, 500)); EXPECT_EQ(compositor_threaded_scrolling_result.scroll_offset.y(), 525u); EXPECT_FALSE(GetScrollNode(scroll_layer)->main_thread_scrolling_reasons); @@ -13129,7 +13771,7 @@ TEST_F(LayerTreeHostImplTest, MainThreadFallback) { GetScrollNode(scroll_layer)->main_thread_scrolling_reasons = MainThreadScrollingReason::kHasNonLayerViewportConstrainedObjects; compositor_threaded_scrolling_result = - host_impl_->MouseDown(gfx::PointF(350, 500), /*shift_modifier*/ false); + host_impl_->MouseDown(gfx::PointF(350, 500), /*jump_key_modifier*/ false); host_impl_->MouseUp(gfx::PointF(350, 500)); EXPECT_EQ(compositor_threaded_scrolling_result.scroll_offset.y(), 0u); @@ -13138,7 +13780,8 @@ TEST_F(LayerTreeHostImplTest, MainThreadFallback) { host_impl_ = nullptr; } -TEST_F(LayerTreeHostImplTest, SecondScrollAnimatedBeginNotIgnored) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + SecondScrollAnimatedBeginNotIgnored) { const gfx::Size content_size(1000, 1000); const gfx::Size viewport_size(50, 100); SetupViewportLayersOuterScrolls(viewport_size, content_size); @@ -13165,7 +13808,8 @@ TEST_F(LayerTreeHostImplTest, SecondScrollAnimatedBeginNotIgnored) { // Verfify that a smooth scroll animation doesn't jump when UpdateTarget gets // called before the animation is started. -TEST_F(LayerTreeHostImplTest, AnimatedScrollUpdateTargetBeforeStarting) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + AnimatedScrollUpdateTargetBeforeStarting) { const gfx::Size content_size(1000, 1000); const gfx::Size viewport_size(50, 100); SetupViewportLayersOuterScrolls(viewport_size, content_size); @@ -13223,7 +13867,7 @@ TEST_F(LayerTreeHostImplTest, AnimatedScrollUpdateTargetBeforeStarting) { EXPECT_TRUE(y > 1 && y < 49); } -TEST_F(LayerTreeHostImplTest, ScrollAnimatedWithDelay) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollAnimatedWithDelay) { const gfx::Size content_size(1000, 1000); const gfx::Size viewport_size(50, 100); SetupViewportLayersOuterScrolls(viewport_size, content_size); @@ -13752,7 +14396,7 @@ TEST_F(LayerTreeHostImplTimelinesTest, ScrollAnimatedChangingBounds) { EXPECT_EQ(gfx::ScrollOffset(250, 250), CurrentScrollOffset(scrolling_layer)); } -TEST_F(LayerTreeHostImplTest, InvalidLayerNotAddedToRasterQueue) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, InvalidLayerNotAddedToRasterQueue) { CreatePendingTree(); Region empty_invalidation; @@ -13786,7 +14430,7 @@ TEST_F(LayerTreeHostImplTest, InvalidLayerNotAddedToRasterQueue) { EXPECT_TRUE(empty_raster_priority_queue_all->IsEmpty()); } -TEST_F(LayerTreeHostImplTest, DidBecomeActive) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, DidBecomeActive) { CreatePendingTree(); host_impl_->ActivateSyncTree(); CreatePendingTree(); @@ -13819,7 +14463,8 @@ TEST_F(LayerTreeHostImplTest, DidBecomeActive) { EXPECT_EQ(2u, raw_mask_layer->did_become_active_call_count()); } -TEST_F(LayerTreeHostImplTest, WheelScrollWithPageScaleFactorOnInnerLayer) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + WheelScrollWithPageScaleFactorOnInnerLayer) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); auto* scroll_layer = InnerViewportScrollLayer(); host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50)); @@ -13892,7 +14537,7 @@ size_t CountRenderPassesWithId(const viz::RenderPassList& list, [id](const std::unique_ptr<viz::RenderPass>& p) { return p->id == id; }); } -TEST_F(LayerTreeHostImplTest, RemoveUnreferencedRenderPass) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, RemoveUnreferencedRenderPass) { TestFrameData frame; frame.render_passes.push_back(viz::RenderPass::Create()); viz::RenderPass* pass3 = frame.render_passes.back().get(); @@ -13927,7 +14572,7 @@ TEST_F(LayerTreeHostImplTest, RemoveUnreferencedRenderPass) { EXPECT_EQ(1u, frame.render_passes[0]->id); } -TEST_F(LayerTreeHostImplTest, RemoveEmptyRenderPass) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, RemoveEmptyRenderPass) { TestFrameData frame; frame.render_passes.push_back(viz::RenderPass::Create()); viz::RenderPass* pass3 = frame.render_passes.back().get(); @@ -13968,7 +14613,7 @@ TEST_F(LayerTreeHostImplTest, RemoveEmptyRenderPass) { pass1->quad_list.ElementAt(0)->material); } -TEST_F(LayerTreeHostImplTest, DoNotRemoveEmptyRootRenderPass) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, DoNotRemoveEmptyRootRenderPass) { TestFrameData frame; frame.render_passes.push_back(viz::RenderPass::Create()); viz::RenderPass* pass3 = frame.render_passes.back().get(); @@ -14024,7 +14669,7 @@ class FakeVideoFrameController : public VideoFrameController { bool did_draw_frame_ = false; }; -TEST_F(LayerTreeHostImplTest, AddVideoFrameControllerInsideFrame) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, AddVideoFrameControllerInsideFrame) { viz::BeginFrameArgs begin_frame_args = viz::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 2); FakeVideoFrameController controller; @@ -14047,7 +14692,8 @@ TEST_F(LayerTreeHostImplTest, AddVideoFrameControllerInsideFrame) { EXPECT_FALSE(controller.did_draw_frame()); } -TEST_F(LayerTreeHostImplTest, AddVideoFrameControllerOutsideFrame) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + AddVideoFrameControllerOutsideFrame) { viz::BeginFrameArgs begin_frame_args = viz::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 2); FakeVideoFrameController controller; @@ -14078,7 +14724,8 @@ TEST_F(LayerTreeHostImplTest, AddVideoFrameControllerOutsideFrame) { } // Tests that SetDeviceScaleFactor correctly impacts GPU rasterization. -TEST_F(LayerTreeHostImplTest, GpuRasterizationStatusDeviceScaleFactor) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + GpuRasterizationStatusDeviceScaleFactor) { // Create a host impl with MSAA support (4 samples). LayerTreeSettings msaaSettings = DefaultSettings(); msaaSettings.gpu_rasterization_msaa_sample_count = -1; @@ -14108,7 +14755,8 @@ TEST_F(LayerTreeHostImplTest, GpuRasterizationStatusDeviceScaleFactor) { } // Tests that explicit MSAA sample count correctly impacts GPU rasterization. -TEST_F(LayerTreeHostImplTest, GpuRasterizationStatusExplicitMSAACount) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + GpuRasterizationStatusExplicitMSAACount) { // Create a host impl with MSAA support and a forced sample count of 4. LayerTreeSettings msaaSettings = DefaultSettings(); msaaSettings.gpu_rasterization_msaa_sample_count = 4; @@ -14245,7 +14893,7 @@ TEST_F(MsaaCompatibilityLayerTreeHostImplTest, 0); } -TEST_F(LayerTreeHostImplTest, UpdatePageScaleFactorOnActiveTree) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, UpdatePageScaleFactorOnActiveTree) { // Check page scale factor update in property trees when an update is made // on the active tree. CreatePendingTree(); @@ -14293,7 +14941,8 @@ TEST_F(LayerTreeHostImplTest, UpdatePageScaleFactorOnActiveTree) { EXPECT_EQ(gfx::Point3F(), active_tree_node->origin); } -TEST_F(LayerTreeHostImplTest, SubLayerScaleForNodeInSubtreeOfPageScaleLayer) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + SubLayerScaleForNodeInSubtreeOfPageScaleLayer) { // Checks that the sublayer scale of a transform node in the subtree of the // page scale layer is updated without a property tree rebuild. host_impl_->active_tree()->PushPageScaleFromMainThread(1, 1, 3); @@ -14321,7 +14970,8 @@ TEST_F(LayerTreeHostImplTest, SubLayerScaleForNodeInSubtreeOfPageScaleLayer) { // Checks that if we lose a GPU raster enabled LayerTreeFrameSink and replace // it with a software LayerTreeFrameSink, LayerTreeHostImpl correctly // re-computes GPU rasterization status. -TEST_F(LayerTreeHostImplTest, RecomputeGpuRasterOnLayerTreeFrameSinkChange) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + RecomputeGpuRasterOnLayerTreeFrameSinkChange) { host_impl_->ReleaseLayerTreeFrameSink(); host_impl_ = nullptr; @@ -14533,7 +15183,7 @@ void LayerTreeHostImplTest::SetupMouseMoveAtTestScrollbarStates( animation_task_.Reset(); // Only the MouseMove's location will affect the overlay scrollbar. - host_impl_->MouseDown(gfx::PointF(60, 50), /*shift_modifier*/ false); + host_impl_->MouseDown(gfx::PointF(60, 50), /*jump_key_modifier*/ false); host_impl_->MouseMoveAt(gfx::Point(60, 50)); host_impl_->MouseUp(gfx::PointF(60, 50)); @@ -14544,29 +15194,30 @@ void LayerTreeHostImplTest::SetupMouseMoveAtTestScrollbarStates( host_impl_->MouseMoveAt(gfx::Point(40, 150)); animation_task_.Reset(); - host_impl_->MouseDown(gfx::PointF(40, 150), /*shift_modifier*/ false); + host_impl_->MouseDown(gfx::PointF(40, 150), /*jump_key_modifier*/ false); host_impl_->MouseUp(gfx::PointF(40, 150)); EXPECT_TRUE(animation_task_.is_null()); // Near scrollbar_1, then mouse down and unregister // scrollbar_2_animation_controller, then mouse up should not cause crash. host_impl_->MouseMoveAt(gfx::Point(40, 150)); - host_impl_->MouseDown(gfx::PointF(40, 150), /*shift_modifier*/ false); - host_impl_->DidUnregisterScrollbarLayer(root_scroll->element_id()); + host_impl_->MouseDown(gfx::PointF(40, 150), /*jump_key_modifier*/ false); + host_impl_->DidUnregisterScrollbarLayer(root_scroll->element_id(), + ScrollbarOrientation::VERTICAL); host_impl_->MouseUp(gfx::PointF(40, 150)); } -TEST_F(LayerTreeHostImplTest, +TEST_P(ScrollUnifiedLayerTreeHostImplTest, LayerTreeHostImplTestScrollbarStatesInMainThreadScrolling) { SetupMouseMoveAtTestScrollbarStates(true); } -TEST_F(LayerTreeHostImplTest, +TEST_P(ScrollUnifiedLayerTreeHostImplTest, LayerTreeHostImplTestScrollbarStatesInNotMainThreadScrolling) { SetupMouseMoveAtTestScrollbarStates(false); } -TEST_F(LayerTreeHostImplTest, CheckerImagingTileInvalidation) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, CheckerImagingTileInvalidation) { LayerTreeSettings settings = LegacySWSettings(); settings.commit_to_active_tree = false; settings.enable_checker_imaging = true; @@ -14651,7 +15302,7 @@ TEST_F(LayerTreeHostImplTest, CheckerImagingTileInvalidation) { EXPECT_EQ(expected_invalidation, *(root->GetPendingInvalidation())); } -TEST_F(LayerTreeHostImplTest, RasterColorSpace) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, RasterColorSpace) { LayerTreeSettings settings = DefaultSettings(); CreateHostImpl(settings, CreateLayerTreeFrameSink()); // The default raster color space should be sRGB. @@ -14666,7 +15317,7 @@ TEST_F(LayerTreeHostImplTest, RasterColorSpace) { gfx::ColorSpace::CreateDisplayP3D65()); } -TEST_F(LayerTreeHostImplTest, RasterColorSpaceSoftware) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, RasterColorSpaceSoftware) { LayerTreeSettings settings = DefaultSettings(); CreateHostImpl(settings, FakeLayerTreeFrameSink::CreateSoftware()); // Software composited resources should always use sRGB as their color space. @@ -14680,7 +15331,7 @@ TEST_F(LayerTreeHostImplTest, RasterColorSpaceSoftware) { gfx::ColorSpace::CreateSRGB()); } -TEST_F(LayerTreeHostImplTest, RasterColorPrefersSRGB) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, RasterColorPrefersSRGB) { auto p3 = gfx::ColorSpace::CreateDisplayP3D65(); LayerTreeSettings settings = DefaultSettings(); @@ -14697,7 +15348,7 @@ TEST_F(LayerTreeHostImplTest, RasterColorPrefersSRGB) { EXPECT_EQ(host_impl_->GetRasterColorSpace(gfx::ContentColorUsage::kSRGB), p3); } -TEST_F(LayerTreeHostImplTest, UpdatedTilingsForNonDrawingLayers) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, UpdatedTilingsForNonDrawingLayers) { gfx::Size layer_bounds(500, 500); CreatePendingTree(); @@ -14749,7 +15400,8 @@ TEST_F(LayerTreeHostImplTest, UpdatedTilingsForNonDrawingLayers) { ->can_require_tiles_for_activation()); } -TEST_F(LayerTreeHostImplTest, RasterTilePrioritizationForNonDrawingLayers) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + RasterTilePrioritizationForNonDrawingLayers) { gfx::Size layer_bounds(500, 500); CreatePendingTree(); auto* root = @@ -14811,7 +15463,7 @@ TEST_F(LayerTreeHostImplTest, RasterTilePrioritizationForNonDrawingLayers) { EXPECT_EQ(queue->Top().tile()->layer_id(), 3); } -TEST_F(LayerTreeHostImplTest, DrawAfterDroppingTileResources) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, DrawAfterDroppingTileResources) { LayerTreeSettings settings = DefaultSettings(); settings.using_synchronous_renderer_compositor = true; CreateHostImpl(settings, CreateLayerTreeFrameSink()); @@ -14845,20 +15497,20 @@ TEST_F(LayerTreeHostImplTest, DrawAfterDroppingTileResources) { EXPECT_GT(layer->tilings()->num_tilings(), 0u); } -TEST_F(LayerTreeHostImplTest, WhiteListedTouchActionTest1) { - WhiteListedTouchActionTestHelper(1.0f, 1.0f); +TEST_P(ScrollUnifiedLayerTreeHostImplTest, AllowedTouchActionTest1) { + AllowedTouchActionTestHelper(1.0f, 1.0f); } -TEST_F(LayerTreeHostImplTest, WhiteListedTouchActionTest2) { - WhiteListedTouchActionTestHelper(1.0f, 0.789f); +TEST_P(ScrollUnifiedLayerTreeHostImplTest, AllowedTouchActionTest2) { + AllowedTouchActionTestHelper(1.0f, 0.789f); } -TEST_F(LayerTreeHostImplTest, WhiteListedTouchActionTest3) { - WhiteListedTouchActionTestHelper(2.345f, 1.0f); +TEST_P(ScrollUnifiedLayerTreeHostImplTest, AllowedTouchActionTest3) { + AllowedTouchActionTestHelper(2.345f, 1.0f); } -TEST_F(LayerTreeHostImplTest, WhiteListedTouchActionTest4) { - WhiteListedTouchActionTestHelper(2.654f, 0.678f); +TEST_P(ScrollUnifiedLayerTreeHostImplTest, AllowedTouchActionTest4) { + AllowedTouchActionTestHelper(2.654f, 0.678f); } // Test implementation of RenderFrameMetadataObserver which can optionally @@ -14893,7 +15545,7 @@ class TestRenderFrameMetadataObserver : public RenderFrameMetadataObserver { base::Optional<RenderFrameMetadata> last_metadata_; }; -TEST_F(LayerTreeHostImplTest, RenderFrameMetadata) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, RenderFrameMetadata) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50)); host_impl_->active_tree()->PushPageScaleFromMainThread(1, 0.5f, 4); @@ -15026,7 +15678,8 @@ TEST_F(LayerTreeHostImplTest, RenderFrameMetadata) { } } -TEST_F(LayerTreeHostImplTest, SelectionBoundsPassedToRenderFrameMetadata) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + SelectionBoundsPassedToRenderFrameMetadata) { LayerImpl* root = SetupDefaultRootLayer(gfx::Size(10, 10)); UpdateDrawProperties(host_impl_->active_tree()); @@ -15078,7 +15731,7 @@ TEST_F(LayerTreeHostImplTest, SelectionBoundsPassedToRenderFrameMetadata) { EXPECT_TRUE(selection_2.end.visible()); } -TEST_F(LayerTreeHostImplTest, +TEST_P(ScrollUnifiedLayerTreeHostImplTest, VerticalScrollDirectionChangesPassedToRenderFrameMetadata) { // Set up the viewport. SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); @@ -15166,7 +15819,8 @@ TEST_F(LayerTreeHostImplTest, // Tests ScrollUpdate() to see if the method sets the scroll tree's currently // scrolling node. -TEST_F(LayerTreeHostImplTest, ScrollUpdateDoesNotSetScrollingNode) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ScrollUpdateDoesNotSetScrollingNode) { SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); UpdateDrawProperties(host_impl_->active_tree()); @@ -15531,7 +16185,8 @@ TEST_F(HitTestRegionListGeneratingLayerTreeHostImplTest, InvalidFrameSinkId) { hit_test_region_list->regions[0].rect.ToString()); } -TEST_F(LayerTreeHostImplTest, ImplThreadPhaseUponImplSideInvalidation) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, + ImplThreadPhaseUponImplSideInvalidation) { LayerTreeSettings settings = DefaultSettings(); CreateHostImpl(settings, CreateLayerTreeFrameSink()); // In general invalidation should never be ran outside the impl frame. @@ -15552,7 +16207,7 @@ TEST_F(LayerTreeHostImplTest, ImplThreadPhaseUponImplSideInvalidation) { // Test passes when there is no crash. } -TEST_F(LayerTreeHostImplTest, SkipOnDrawDoesNotUpdateDrawParams) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, SkipOnDrawDoesNotUpdateDrawParams) { EXPECT_TRUE(CreateHostImpl(DefaultSettings(), FakeLayerTreeFrameSink::CreateSoftware())); SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); @@ -15581,7 +16236,7 @@ TEST_F(LayerTreeHostImplTest, SkipOnDrawDoesNotUpdateDrawParams) { // Test that a touch scroll over a SolidColorScrollbarLayer, the scrollbar used // on Android, does not register as a scrollbar scroll and result in main // threaded scrolling. -TEST_F(LayerTreeHostImplTest, TouchScrollOnAndroidScrollbar) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, TouchScrollOnAndroidScrollbar) { LayerTreeImpl* layer_tree_impl = host_impl_->active_tree(); gfx::Size viewport_size = gfx::Size(360, 600); gfx::Size scroll_content_size = gfx::Size(360, 3800); @@ -15786,7 +16441,7 @@ TEST_F(ForceActivateAfterPaintWorkletPaintLayerTreeHostImplTest, // Verify that the device scale factor is not used to rescale scrollbar deltas // in percent-based scrolling. -TEST_F(LayerTreeHostImplTest, PercentBasedScrollbarDeltasDSF3) { +TEST_P(ScrollUnifiedLayerTreeHostImplTest, PercentBasedScrollbarDeltasDSF3) { LayerTreeSettings settings = DefaultSettings(); settings.percent_based_scrolling = true; settings.use_zoom_for_dsf = true; @@ -15798,21 +16453,21 @@ TEST_F(LayerTreeHostImplTest, PercentBasedScrollbarDeltasDSF3) { LayerImpl* content_layer = AddContentLayer(); LayerImpl* scroll_layer = AddScrollableLayer( - content_layer, gfx::Size(185, 200), gfx::Size(185, 3800)); + content_layer, gfx::Size(185, 500), gfx::Size(185, 3800)); auto* scrollbar = AddLayer<PaintedScrollbarLayerImpl>( host_impl_->active_tree(), VERTICAL, false, true); SetupScrollbarLayer(scroll_layer, scrollbar); - scrollbar->SetBounds(gfx::Size(15, 200)); + scrollbar->SetBounds(gfx::Size(15, 500)); scrollbar->SetThumbThickness(15); scrollbar->SetThumbLength(50); - scrollbar->SetTrackRect(gfx::Rect(0, 15, 15, 185)); + scrollbar->SetTrackRect(gfx::Rect(0, 15, 15, 485)); scrollbar->SetBackButtonRect(gfx::Rect(gfx::Point(0, 0), gfx::Size(15, 15))); scrollbar->SetForwardButtonRect( - gfx::Rect(gfx::Point(0, 185), gfx::Size(15, 15))); + gfx::Rect(gfx::Point(0, 485), gfx::Size(15, 15))); scrollbar->SetOffsetToTransformParent(gfx::Vector2dF(185, 0)); DrawFrame(); @@ -15821,13 +16476,13 @@ TEST_F(LayerTreeHostImplTest, PercentBasedScrollbarDeltasDSF3) { host_impl_->BindToClient(&input_handler_client); // Test scrolling with device scale factor = 3. - const float expected_delta = kPercentDeltaForDirectionalScroll * 200u; + const float expected_delta = floorf(kPercentDeltaForDirectionalScroll * 500); host_impl_->active_tree()->set_painted_device_scale_factor(3); InputHandlerPointerResult scroll_result = - host_impl_->MouseDown(gfx::PointF(190, 190), false); - host_impl_->MouseUp(gfx::PointF(190, 190)); + host_impl_->MouseDown(gfx::PointF(190, 490), false); + host_impl_->MouseUp(gfx::PointF(190, 490)); EXPECT_EQ(scroll_result.scroll_offset.y(), expected_delta); EXPECT_FALSE(GetScrollNode(scroll_layer)->main_thread_scrolling_reasons); @@ -15837,8 +16492,8 @@ TEST_F(LayerTreeHostImplTest, PercentBasedScrollbarDeltasDSF3) { host_impl_->active_tree()->set_painted_device_scale_factor(1); InputHandlerPointerResult scroll_with_dsf_1 = - host_impl_->MouseDown(gfx::PointF(190, 190), false); - host_impl_->MouseUp(gfx::PointF(190, 190)); + host_impl_->MouseDown(gfx::PointF(190, 490), false); + host_impl_->MouseUp(gfx::PointF(190, 490)); EXPECT_EQ(scroll_with_dsf_1.scroll_offset.y(), expected_delta); EXPECT_FALSE(GetScrollNode(scroll_layer)->main_thread_scrolling_reasons); @@ -15848,5 +16503,497 @@ TEST_F(LayerTreeHostImplTest, PercentBasedScrollbarDeltasDSF3) { host_impl_ = nullptr; } +class UnifiedScrollingTest : public LayerTreeHostImplTest { + public: + using Point = gfx::Point; + using ScrollInputType = ui::ScrollInputType; + using ScrollOffset = gfx::ScrollOffset; + using ScrollStatus = InputHandler::ScrollStatus; + using Size = gfx::Size; + using Rect = gfx::Rect; + using Vector2d = gfx::Vector2d; + + void SetUp() override { + scoped_feature_list.InitAndEnableFeature(features::kScrollUnification); + LayerTreeHostImplTest::SetUp(); + + cur_time_ = base::TimeTicks() + base::TimeDelta::FromMilliseconds(100); + begin_frame_args_ = + viz::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1); + + SetupViewportLayersOuterScrolls(gfx::Size(100, 100), gfx::Size(200, 200)); + } + + void CreateUncompositedScrollerAndNonFastScrollableRegion() { + // Create an uncompositd scroll node that corresponds to a non fast + // scrollable region on the outer viewport scroll layer. + Size scrollable_content_bounds(100, 100); + Size container_bounds(50, 50); + CreateScrollNodeForUncompositedScroller( + GetPropertyTrees(), host_impl_->OuterViewportScrollNode()->id, + ScrollerElementId(), scrollable_content_bounds, container_bounds); + OuterViewportScrollLayer()->SetNonFastScrollableRegion(Rect(0, 0, 50, 50)); + + host_impl_->active_tree()->set_needs_update_draw_properties(); + UpdateDrawProperties(host_impl_->active_tree()); + host_impl_->active_tree()->DidBecomeActive(); + DrawFrame(); + } + + void CreateScroller(uint32_t main_thread_scrolling_reasons) { + // Creates a regular compositeds scroller that comes with a ScrollNode and + // Layer. + Size scrollable_content_bounds(100, 100); + Size container_bounds(50, 50); + + LayerImpl* layer = + AddScrollableLayer(OuterViewportScrollLayer(), container_bounds, + scrollable_content_bounds); + scroller_layer_ = layer; + GetScrollNode(layer)->main_thread_scrolling_reasons = + main_thread_scrolling_reasons; + GetScrollNode(layer)->is_composited = true; + + UpdateDrawProperties(host_impl_->active_tree()); + host_impl_->active_tree()->DidBecomeActive(); + DrawFrame(); + } + + void CreateSuashingLayerCoveringWholeViewport() { + // Add a (simulated) "squashing layer". It's scroll parent is the outer + // viewport but it covers the entire viewport and any scrollers underneath + // it. + LayerImpl* squashing_layer = AddLayer(); + squashing_layer->SetBounds(gfx::Size(100, 100)); + squashing_layer->SetDrawsContent(true); + squashing_layer->SetHitTestable(true); + CopyProperties(OuterViewportScrollLayer(), squashing_layer); + UpdateDrawProperties(host_impl_->active_tree()); + } + + ScrollStatus ScrollBegin(const Vector2d& delta) { + auto scroll_state = + BeginState(Point(25, 25), delta, ScrollInputType::kWheel); + ScrollStatus status = + host_impl_->ScrollBegin(scroll_state.get(), ScrollInputType::kWheel); + + if (status.needs_main_thread_hit_test) + to_be_continued_scroll_begin_ = std::move(scroll_state); + + return status; + } + + ScrollStatus ContinuedScrollBegin(ElementId element_id) { + DCHECK(to_be_continued_scroll_begin_) + << "ContinuedScrollBegin needs to come after a ScrollBegin that " + "requested a main frame"; + std::unique_ptr<ScrollState> scroll_state = + std::move(to_be_continued_scroll_begin_); + + scroll_state->data()->set_current_native_scrolling_element(element_id); + scroll_state->data()->is_main_thread_hit_tested = true; + + return host_impl_->ScrollBegin(scroll_state.get(), ScrollInputType::kWheel); + } + + InputHandlerScrollResult ScrollUpdate(const Vector2d& delta) { + auto scroll_state = + UpdateState(Point(25, 25), delta, ScrollInputType::kWheel); + return host_impl_->ScrollUpdate(scroll_state.get()); + } + + InputHandlerScrollResult AnimatedScrollUpdate(const Vector2d& delta) { + auto scroll_state = AnimatedUpdateState(Point(25, 25), delta); + return host_impl_->ScrollUpdate(scroll_state.get()); + } + + void ScrollEnd() { return host_impl_->ScrollEnd(); } + + // An animation is setup in the first BeginFrame so it won't actually update + // in the first one. Use a named method for that so it's clear rather than + // mysteriously calling BeginFrame twice. + void StartAnimation() { BeginFrame(base::TimeDelta()); } + + void BeginFrame(base::TimeDelta forward) { + cur_time_ += forward; + begin_frame_args_.frame_time = cur_time_; + begin_frame_args_.frame_id.sequence_number++; + host_impl_->WillBeginImplFrame(begin_frame_args_); + host_impl_->Animate(); + host_impl_->UpdateAnimationState(true); + host_impl_->DidFinishImplFrame(begin_frame_args_); + } + + ScrollOffset GetScrollOffset(ScrollNode* node) { + return GetPropertyTrees()->scroll_tree.current_scroll_offset( + node->element_id); + } + + ScrollOffset ScrollerOffset() { + return GetPropertyTrees()->scroll_tree.current_scroll_offset( + ScrollerElementId()); + } + + PropertyTrees* GetPropertyTrees() { + return host_impl_->active_tree()->property_trees(); + } + + ScrollNode* CurrentlyScrollingNode() { + return host_impl_->CurrentlyScrollingNode(); + } + + ScrollNode* ScrollerNode() { + ScrollNode* node = GetPropertyTrees()->scroll_tree.FindNodeFromElementId( + ScrollerElementId()); + DCHECK(node); + return node; + } + ElementId ScrollerElementId() const { + if (scroller_layer_) + return scroller_layer_->element_id(); + + return ElementId(1234); + } + + base::TimeDelta kFrameInterval = base::TimeDelta::FromMilliseconds(16); + + // Parameterized test body. Defined inline with tests. + void TestUncompositedScrollingState(bool mutates_transform_tree); + + private: + LayerImpl* scroller_layer_ = nullptr; + + base::TimeTicks cur_time_; + viz::BeginFrameArgs begin_frame_args_; + + std::unique_ptr<ScrollState> to_be_continued_scroll_begin_; + base::test::ScopedFeatureList scoped_feature_list; +}; + +// A ScrollBegin that hits a non fast scrollable region must return a request +// for a main thread hit test. +TEST_F(UnifiedScrollingTest, UnifiedScrollNonFastScrollableRegion) { + CreateUncompositedScrollerAndNonFastScrollableRegion(); + + // When ScrollUnification is turned on, scrolling inside a non-fast + // scrollable region should request a main thread hit test. It's the client's + // responsibility to request a hit test from Blink. It can then call + // ScrollBegin again, providing the element_id to scroll. + { + ScrollStatus status = ScrollBegin(Vector2d(0, 10)); + + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_TRUE(status.needs_main_thread_hit_test); + + // The scroll hasn't started yet though. + EXPECT_FALSE(CurrentlyScrollingNode()); + } + + // Simulate the scroll hit test coming back from the main thread. This time + // ScrollBegin will be called with an element id provided so that a hit test + // is unnecessary. + { + ScrollStatus status = ContinuedScrollBegin(ScrollerElementId()); + + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_FALSE(status.needs_main_thread_hit_test); + + EXPECT_TRUE(CurrentlyScrollingNode()); + EXPECT_EQ(ScrollerNode(), CurrentlyScrollingNode()); + } + + // Ensure ScrollUpdates can successfully scroll this node. They shouldn't + // mutate the associated transform node. + { + EXPECT_TRUE(ScrollUpdate(Vector2d(0, 10)).did_scroll); + EXPECT_EQ(ScrollOffset(0, 10), ScrollerOffset()); + } + + // Try to scroll past the end. Ensure the max scrolling bounds are respected. + { + EXPECT_TRUE(ScrollUpdate(Vector2d(0, 1000)).did_scroll); + EXPECT_EQ(ScrollOffset(0, 50), ScrollerOffset()); + } + + // Overscrolling should cause the scroll update to be dropped. + { + EXPECT_FALSE(ScrollUpdate(Vector2d(0, 10)).did_scroll); + EXPECT_EQ(ScrollOffset(0, 50), ScrollerOffset()); + } + + host_impl_->ScrollEnd(); +} + +// A main thread hit test should still go through latch bubbling. That is, if +// the hit tested scroller is fully scrolled and cannot consume the scroll, we +// should chain up to its ancestor. +TEST_F(UnifiedScrollingTest, MainThreadHitTestLatchBubbling) { + CreateUncompositedScrollerAndNonFastScrollableRegion(); + + // Start with the scroller fully scrolled. + { + ScrollBegin(Vector2d(0, 1000)); + ContinuedScrollBegin(ScrollerElementId()); + ScrollUpdate(Vector2d(0, 1000)); + ScrollEnd(); + ASSERT_EQ(ScrollOffset(0, 50), ScrollerOffset()); + } + + { + ScrollStatus status = ScrollBegin(Vector2d(0, 10)); + ASSERT_TRUE(status.needs_main_thread_hit_test); + status = ContinuedScrollBegin(ScrollerElementId()); + + // Since the hit tested scroller in ContinuedScrollBegin was fully + // scrolled, we should latch to the viewport instead. + EXPECT_TRUE(CurrentlyScrollingNode()); + EXPECT_EQ(host_impl_->OuterViewportScrollNode(), CurrentlyScrollingNode()); + } +} + +using UnifiedScrollingDeathTest = UnifiedScrollingTest; + +// A main thread hit test that with an empty target id should be dropped. +TEST_F(UnifiedScrollingDeathTest, EmptyMainThreadHitTest) { + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + CreateUncompositedScrollerAndNonFastScrollableRegion(); + { + ElementId kInvalidId; + DCHECK(!kInvalidId); + + ScrollStatus status = ScrollBegin(Vector2d(0, 10)); + + // Note, we have a NOTREACHED here to make sure this cannot happen. If + // DCHECKs are enabled we can just make sure we get killed when we end up + // in this situation. +#if DCHECK_IS_ON() + EXPECT_DEATH_IF_SUPPORTED({ status = ContinuedScrollBegin(kInvalidId); }, + ""); +#else + status = ContinuedScrollBegin(kInvalidId); + EXPECT_EQ(InputHandler::SCROLL_IGNORED, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kNoScrollingLayer, + status.main_thread_scrolling_reasons); +#endif + } +} + +// A main thread hit test that returns a scroll node we can't find should be +// dropped. +TEST_F(UnifiedScrollingTest, MainThreadHitTestScrollNodeNotFound) { + CreateUncompositedScrollerAndNonFastScrollableRegion(); + + { + ElementId kUnknown(42); + DCHECK(!GetPropertyTrees()->scroll_tree.FindNodeFromElementId(kUnknown)); + + ScrollStatus status = ScrollBegin(Vector2d(0, 10)); + status = ContinuedScrollBegin(kUnknown); + EXPECT_EQ(InputHandler::SCROLL_IGNORED, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kNoScrollingLayer, + status.main_thread_scrolling_reasons); + } +} + +// The presence of a squashing layer is one reason we might "fail to hit test" +// on the compositor. A heuristic is used that if the first hit scrollable +// layer isn't a scrolling ancestor of the first hit layer, we probably hit a +// squashing layer which might have empty regions we don't know how to hit +// test. This requires falling back to the main thread. However, with scroll +// unification, we may still be able to scroll the final scroller entirely on +// the compositor. See LayerTreeHostImpl::IsInitialScrollHitTestReliable. +TEST_F(UnifiedScrollingTest, SquashingLayerCausesMainThreadHitTest) { + // Create a scroller that should scroll on the compositor thread and the add + // "squashing" layer over top of it. This simulates the case where a + // squashing layer obscuring a scroller makes the hit test unreliable. + CreateScroller(MainThreadScrollingReason::kNotScrollingOnMain); + CreateSuashingLayerCoveringWholeViewport(); + + // When ScrollUnification is turned on, scrolling over a squashing-like layer + // that cannot be reliably hit tested on the compositor should request a main + // thread hit test. + { + ScrollStatus status = ScrollBegin(Vector2d(0, 10)); + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_TRUE(status.needs_main_thread_hit_test); + } + + // Resolving the hit test should allow the scroller underneath to scroll as + // normal on the impl thread. + { + ScrollStatus status = ContinuedScrollBegin(ScrollerElementId()); + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_FALSE(status.needs_main_thread_hit_test); + + EXPECT_TRUE(CurrentlyScrollingNode()); + EXPECT_EQ(ScrollerNode(), CurrentlyScrollingNode()); + } +} + +// Under unified scroling, a composited scroller with a main thread scrolling +// reason should be scrolled on the compositor. Ensure ScrollBegin returns +// success without needing a main thread hit test. +TEST_F(UnifiedScrollingTest, MainThreadScrollingReasonsScrollOnCompositor) { + CreateScroller( + MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects); + + { + ScrollStatus status = ScrollBegin(Vector2d(0, 10)); + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_FALSE(status.needs_main_thread_hit_test); + } +} + +// This tests whether or not various kinds of scrolling mutates the transform +// tree or not. It is parameterized and used by tests below. +void UnifiedScrollingTest::TestUncompositedScrollingState( + bool mutates_transform_tree) { + TransformTree& tree = GetPropertyTrees()->transform_tree; + TransformNode* transform_node = tree.Node(ScrollerNode()->transform_id); + + // Ensure we're in a clean state to start. + { + ASSERT_EQ(transform_node->element_id, ScrollerElementId()); + ASSERT_TRUE(transform_node->scrolls); + + ASSERT_EQ(ScrollOffset(0, 0), transform_node->scroll_offset); + ASSERT_FALSE(transform_node->transform_changed); + ASSERT_FALSE(transform_node->needs_local_transform_update); + ASSERT_FALSE(tree.needs_update()); + } + + // Start a scroll, ensure the scroll tree was updated and a commit was + // requested. Check that the transform tree mutation was as expected for the + // test parameter. + { + ScrollStatus status = ScrollBegin(Vector2d(0, 10)); + if (status.needs_main_thread_hit_test) + ContinuedScrollBegin(ScrollerElementId()); + + ASSERT_EQ(ScrollerNode(), CurrentlyScrollingNode()); + + did_request_commit_ = false; + + ScrollUpdate(Vector2d(0, 10)); + ASSERT_EQ(ScrollOffset(0, 10), ScrollerOffset()); + EXPECT_TRUE(did_request_commit_); + + // Ensure the transform tree was updated only if expected. + if (mutates_transform_tree) + EXPECT_EQ(ScrollOffset(0, 10), transform_node->scroll_offset); + else + EXPECT_EQ(ScrollOffset(0, 0), transform_node->scroll_offset); + EXPECT_EQ(mutates_transform_tree, transform_node->transform_changed); + EXPECT_EQ(mutates_transform_tree, + transform_node->needs_local_transform_update); + EXPECT_EQ(mutates_transform_tree, tree.needs_update()); + } + + // Perform animated scroll update. Ensure the same things. + { + did_request_commit_ = false; + + AnimatedScrollUpdate(Vector2d(0, 10)); + ASSERT_TRUE(host_impl_->mutator_host()->ImplOnlyScrollAnimatingElement()); + ASSERT_EQ(ScrollOffset(0, 10), ScrollerOffset()); + + StartAnimation(); + BeginFrame(kFrameInterval); + BeginFrame(base::TimeDelta::FromMilliseconds(500)); + BeginFrame(kFrameInterval); + + ASSERT_EQ(ScrollOffset(0, 20), ScrollerOffset()); + EXPECT_TRUE(did_request_commit_); + + if (mutates_transform_tree) + EXPECT_EQ(ScrollOffset(0, 20), transform_node->scroll_offset); + else + EXPECT_EQ(ScrollOffset(0, 0), transform_node->scroll_offset); + EXPECT_EQ(mutates_transform_tree, transform_node->transform_changed); + EXPECT_EQ(mutates_transform_tree, + transform_node->needs_local_transform_update); + EXPECT_EQ(mutates_transform_tree, tree.needs_update()); + } +} + +// When scrolling a main-thread hit tested scroller with main thread reasons, +// we should update the scroll node but the transform tree shouldn't be +// mutated. Also ensure NeedsCommit is set. A nice additional benefit of scroll +// unification should be seamless upgrade to a full compositor scroll if a main +// thread reason is removed. +TEST_F(UnifiedScrollingTest, MainThreadReasonsScrollDoesntAffectTransform) { + CreateScroller( + MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects); + + TestUncompositedScrollingState(/*mutates_transform_tree=*/false); + + ASSERT_EQ(ScrollerNode()->main_thread_scrolling_reasons, + MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects); + TransformTree& tree = GetPropertyTrees()->transform_tree; + TransformNode* transform_node = tree.Node(ScrollerNode()->transform_id); + + // Removing the main thread reason bit should start mutating the transform + // tree. + { + ScrollerNode()->main_thread_scrolling_reasons = + MainThreadScrollingReason::kNotScrollingOnMain; + UpdateDrawProperties(host_impl_->active_tree()); + host_impl_->active_tree()->DidBecomeActive(); + + ScrollUpdate(Vector2d(0, 10)); + ASSERT_EQ(ScrollOffset(0, 30), ScrollerOffset()); + + // The transform node should now be updated by the scroll. + EXPECT_EQ(ScrollOffset(0, 30), transform_node->scroll_offset); + EXPECT_TRUE(transform_node->transform_changed); + EXPECT_TRUE(transform_node->needs_local_transform_update); + EXPECT_TRUE(tree.needs_update()); + } + + ScrollEnd(); +} + +// When scrolling an uncomposited scroller, we shouldn't modify the transform +// tree. If a scroller is promoted mid-scroll it should start mutating the +// transform tree. +TEST_F(UnifiedScrollingTest, UncompositedScrollerDoesntAffectTransform) { + CreateUncompositedScrollerAndNonFastScrollableRegion(); + + TestUncompositedScrollingState(/*mutates_transform_tree=*/false); + + ASSERT_FALSE(ScrollerNode()->is_composited); + TransformTree& tree = GetPropertyTrees()->transform_tree; + TransformNode* transform_node = tree.Node(ScrollerNode()->transform_id); + + // Marking the node as composited should start updating the transform tree. + { + ScrollerNode()->is_composited = true; + UpdateDrawProperties(host_impl_->active_tree()); + host_impl_->active_tree()->DidBecomeActive(); + + ScrollUpdate(Vector2d(0, 10)); + ASSERT_EQ(ScrollOffset(0, 30), ScrollerOffset()); + + // The transform node should now be updated by the scroll. + EXPECT_EQ(ScrollOffset(0, 30), transform_node->scroll_offset); + EXPECT_TRUE(transform_node->transform_changed); + EXPECT_TRUE(transform_node->needs_local_transform_update); + EXPECT_TRUE(tree.needs_update()); + } + + ScrollEnd(); +} + +// When scrolling a composited scroller that just happens to have needed a main +// thread hit test first, we should modify the transform tree as usual. +TEST_F(UnifiedScrollingTest, CompositedWithSquashedLayerMutatesTransform) { + CreateScroller(MainThreadScrollingReason::kNotScrollingOnMain); + CreateSuashingLayerCoveringWholeViewport(); + + TestUncompositedScrollingState(/*mutates_transform_tree=*/true); + + ScrollEnd(); +} + } // namespace } // namespace cc diff --git a/chromium/cc/trees/layer_tree_host_pixeltest_blending.cc b/chromium/cc/trees/layer_tree_host_pixeltest_blending.cc index b55a4b3b733..0a6809e778a 100644 --- a/chromium/cc/trees/layer_tree_host_pixeltest_blending.cc +++ b/chromium/cc/trees/layer_tree_host_pixeltest_blending.cc @@ -6,12 +6,13 @@ #include "base/stl_util.h" #include "build/build_config.h" -#include "cc/layers/picture_image_layer.h" #include "cc/layers/solid_color_layer.h" #include "cc/paint/paint_image.h" #include "cc/paint/paint_image_builder.h" #include "cc/paint/render_surface_filters.h" #include "cc/paint/skia_paint_canvas.h" +#include "cc/test/fake_content_layer_client.h" +#include "cc/test/fake_picture_layer.h" #include "cc/test/layer_tree_pixel_resource_test.h" #include "cc/test/pixel_comparator.h" #include "cc/test/test_layer_tree_frame_sink.h" @@ -118,23 +119,19 @@ class LayerTreeHostBlendingPixelTest scoped_refptr<Layer> CreateColorfulBackdropLayer(int width, int height) { sk_sp<SkSurface> backing_store = CreateColorfulSurface(width, height); - scoped_refptr<PictureImageLayer> layer = PictureImageLayer::Create(); + gfx::Size bounds(width, height); + backdrop_client_.set_bounds(bounds); + backdrop_client_.add_draw_image(backing_store->makeImageSnapshot(), + gfx::Point(), PaintFlags()); + scoped_refptr<FakePictureLayer> layer = + FakePictureLayer::Create(&backdrop_client_); layer->SetIsDrawable(true); - layer->SetBounds(gfx::Size(width, height)); - layer->SetImage(PaintImageBuilder::WithDefault() - .set_id(PaintImage::GetNextId()) - .set_image(backing_store->makeImageSnapshot(), - PaintImage::GetNextContentId()) - .TakePaintImage(), - SkMatrix::I(), false); + layer->SetBounds(bounds); return layer; } void SetupMaskLayer(scoped_refptr<Layer> layer) { gfx::Size bounds = layer->bounds(); - scoped_refptr<PictureImageLayer> mask = PictureImageLayer::Create(); - mask->SetIsDrawable(true); - mask->SetBounds(bounds); sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(bounds.width(), bounds.height()); @@ -146,12 +143,15 @@ class LayerTreeHostBlendingPixelTest // cover the right half of it canvas->drawRect( SkRect::MakeXYWH(1, 0, bounds.width() - 1, bounds.height()), paint); - mask->SetImage(PaintImageBuilder::WithDefault() - .set_id(PaintImage::GetNextId()) - .set_image(surface->makeImageSnapshot(), - PaintImage::GetNextContentId()) - .TakePaintImage(), - SkMatrix::I(), false); + + mask_client_.set_bounds(bounds); + mask_client_.add_draw_image(surface->makeImageSnapshot(), gfx::Point(), + PaintFlags()); + + scoped_refptr<FakePictureLayer> mask = + FakePictureLayer::Create(&mask_client_); + mask->SetIsDrawable(true); + mask->SetBounds(bounds); layer->SetMaskLayer(mask); } @@ -254,6 +254,8 @@ class LayerTreeHostBlendingPixelTest bool force_antialiasing_; bool force_blending_with_shaders_; + FakeContentLayerClient mask_client_; + FakeContentLayerClient backdrop_client_; SkColor misc_opaque_color_ = 0xffc86464; }; diff --git a/chromium/cc/trees/layer_tree_host_pixeltest_filters.cc b/chromium/cc/trees/layer_tree_host_pixeltest_filters.cc index c93b84e3271..4042e97367c 100644 --- a/chromium/cc/trees/layer_tree_host_pixeltest_filters.cc +++ b/chromium/cc/trees/layer_tree_host_pixeltest_filters.cc @@ -309,55 +309,6 @@ TEST_P(LayerTreeHostFiltersPixelTest, BackdropFilterBlurOutsets) { base::FilePath(FILE_PATH_LITERAL("backdrop_filter_blur_outsets.png"))); } -class LayerTreeHostImageFiltersPixelTestLayerList - : public LayerTreeHostFiltersPixelTest { - public: - LayerTreeHostImageFiltersPixelTestLayerList() { SetUseLayerLists(); } - - void SetupTree() override { - SetInitialRootBounds(gfx::Size(200, 200)); - LayerTreePixelTest::SetupTree(); - - Layer* root = layer_tree_host()->root_layer(); - scoped_refptr<SolidColorLayer> background = - CreateSolidColorLayer(gfx::Rect(200, 200), SK_ColorYELLOW); - CopyProperties(root, background.get()); - root->AddChild(background); - - scoped_refptr<SolidColorLayer> foreground = - CreateSolidColorLayer(gfx::Rect(200, 200), SK_ColorRED); - CopyProperties(root, foreground.get()); - root->AddChild(foreground); - - EffectNode& effect_node = CreateEffectNode(foreground.get()); - float matrix[20] = {0}; - // This filter does a red-blue swap, so the foreground becomes blue. - matrix[2] = matrix[6] = matrix[10] = matrix[18] = 1.0f; - // Set up a crop rect to filter the bottom 200x100 pixels of the foreground. - SkImageFilter::CropRect crop_rect(SkRect::MakeXYWH(0, 100, 200, 100)); - FilterOperations filters; - filters.Append(FilterOperation::CreateReferenceFilter( - sk_make_sp<ColorFilterPaintFilter>(SkColorFilters::Matrix(matrix), - nullptr, &crop_rect))); - - effect_node.filters = filters; - effect_node.render_surface_reason = RenderSurfaceReason::kFilter; - - // Move the filters origin up by 100 pixels so the crop rect is applied - // only to the top 100 pixels, not the bottom. - effect_node.filters_origin = gfx::PointF(0.0f, -100.0f); - } -}; - -INSTANTIATE_TEST_SUITE_P(All, - LayerTreeHostImageFiltersPixelTestLayerList, - ::testing::ValuesIn(kRendererTypes)); - -TEST_P(LayerTreeHostImageFiltersPixelTestLayerList, NonZeroOrigin) { - RunPixelTestWithLayerList( - base::FilePath(FILE_PATH_LITERAL("blue_yellow.png"))); -} - class LayerTreeHostBlurFiltersPixelTestGPULayerList : public LayerTreeHostFiltersPixelTest { public: diff --git a/chromium/cc/trees/layer_tree_host_pixeltest_masks.cc b/chromium/cc/trees/layer_tree_host_pixeltest_masks.cc index 7e39b785622..702d4e5dbb7 100644 --- a/chromium/cc/trees/layer_tree_host_pixeltest_masks.cc +++ b/chromium/cc/trees/layer_tree_host_pixeltest_masks.cc @@ -7,13 +7,13 @@ #include "base/stl_util.h" #include "build/build_config.h" #include "cc/layers/content_layer_client.h" -#include "cc/layers/picture_image_layer.h" #include "cc/layers/picture_layer.h" #include "cc/layers/solid_color_layer.h" #include "cc/paint/paint_flags.h" #include "cc/paint/paint_image.h" #include "cc/paint/paint_image_builder.h" #include "cc/paint/paint_op_buffer.h" +#include "cc/test/fake_content_layer_client.h" #include "cc/test/fake_picture_layer.h" #include "cc/test/layer_tree_pixel_resource_test.h" #include "cc/test/pixel_comparator.h" @@ -318,22 +318,20 @@ TEST_P(LayerTreeHostMaskPixelTestWithLayerList, MaskWithEffectDifferentSize) { } TEST_P(LayerTreeHostMaskPixelTestWithLayerList, ImageMaskWithEffect) { - MaskContentLayerClient client(mask_bounds_); - scoped_refptr<PictureImageLayer> mask_layer = PictureImageLayer::Create(); + MaskContentLayerClient mask_client(mask_bounds_); sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(50, 50); SkCanvas* canvas = surface->getCanvas(); scoped_refptr<DisplayItemList> mask_display_list = - client.PaintContentsToDisplayList( + mask_client.PaintContentsToDisplayList( ContentLayerClient::PAINTING_BEHAVIOR_NORMAL); mask_display_list->Raster(canvas); - mask_layer->SetImage(PaintImageBuilder::WithDefault() - .set_id(PaintImage::GetNextId()) - .set_image(surface->makeImageSnapshot(), - PaintImage::GetNextContentId()) - .TakePaintImage(), - SkMatrix::I(), false); - mask_layer_ = mask_layer; + + FakeContentLayerClient layer_client; + layer_client.set_bounds(mask_bounds_); + layer_client.add_draw_image(surface->makeImageSnapshot(), gfx::Point(), + PaintFlags()); + mask_layer_ = FakePictureLayer::Create(&layer_client); pixel_comparator_ = std::make_unique<FuzzyPixelOffByOneComparator>(true /* discard_alpha */); @@ -350,10 +348,6 @@ TEST_P(LayerTreeHostMasksPixelTest, ImageMaskOfLayer) { gfx::Size mask_bounds(50, 50); - scoped_refptr<PictureImageLayer> mask = PictureImageLayer::Create(); - mask->SetIsDrawable(true); - mask->SetBounds(mask_bounds); - sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(50, 50); SkCanvas* canvas = surface->getCanvas(); MaskContentLayerClient client(mask_bounds); @@ -361,12 +355,14 @@ TEST_P(LayerTreeHostMasksPixelTest, ImageMaskOfLayer) { client.PaintContentsToDisplayList( ContentLayerClient::PAINTING_BEHAVIOR_NORMAL); mask_display_list->Raster(canvas); - mask->SetImage(PaintImageBuilder::WithDefault() - .set_id(PaintImage::GetNextId()) - .set_image(surface->makeImageSnapshot(), - PaintImage::GetNextContentId()) - .TakePaintImage(), - SkMatrix::I(), false); + + FakeContentLayerClient mask_client; + mask_client.set_bounds(mask_bounds); + mask_client.add_draw_image(surface->makeImageSnapshot(), gfx::Point(), + PaintFlags()); + scoped_refptr<FakePictureLayer> mask = FakePictureLayer::Create(&mask_client); + mask->SetIsDrawable(true); + mask->SetBounds(mask_bounds); scoped_refptr<SolidColorLayer> green = CreateSolidColorLayerWithBorder( gfx::Rect(25, 25, 50, 50), kCSSGreen, 1, SK_ColorBLACK); diff --git a/chromium/cc/trees/layer_tree_host_pixeltest_synchronous.cc b/chromium/cc/trees/layer_tree_host_pixeltest_synchronous.cc index 2739d9880bb..34d9ef117a4 100644 --- a/chromium/cc/trees/layer_tree_host_pixeltest_synchronous.cc +++ b/chromium/cc/trees/layer_tree_host_pixeltest_synchronous.cc @@ -4,7 +4,6 @@ #include "build/build_config.h" #include "cc/layers/content_layer_client.h" -#include "cc/layers/picture_image_layer.h" #include "cc/layers/picture_layer.h" #include "cc/layers/solid_color_layer.h" #include "cc/test/fake_content_layer_client.h" diff --git a/chromium/cc/trees/layer_tree_host_unittest.cc b/chromium/cc/trees/layer_tree_host_unittest.cc index edbee8ea336..2d94507a792 100644 --- a/chromium/cc/trees/layer_tree_host_unittest.cc +++ b/chromium/cc/trees/layer_tree_host_unittest.cc @@ -52,7 +52,6 @@ #include "cc/test/test_layer_tree_frame_sink.h" #include "cc/trees/clip_node.h" #include "cc/trees/effect_node.h" -#include "cc/trees/frame_rate_counter.h" #include "cc/trees/layer_tree_host_impl.h" #include "cc/trees/layer_tree_impl.h" #include "cc/trees/scroll_and_scale_set.h" @@ -6187,9 +6186,9 @@ class LayerTreeHostTestSwapPromiseDuringCommit : public LayerTreeHostTest { &set_needs_commit_count, &set_needs_redraw_count)); layer_tree_host()->QueueSwapPromise(std::move(swap_promise)); - // Queueing a swap promise from DidBeginMainFrame should cause a + // Queueing a swap promise from DidBeginMainFrame should not cause a // subsequent main frame to be scheduled. - EXPECT_EQ(1, set_needs_commit_count); + EXPECT_EQ(0, set_needs_commit_count); } EndTest(); @@ -8902,28 +8901,11 @@ class LayerTreeHostCustomThrougputTrackerTest : public LayerTreeHostTest { } void NotifyThroughputTrackerResults(CustomTrackerResults results) override { + // Check that data for kSequenceId is captured. Ideally, we should get + // 2 frame_expected and 2 frame_produced. But on slow bots, it is difficult + // to infer the correct numbers. Both frame_expected and frame_produced + // could drop to 1 (or even below). So no sanity check on data itself. ASSERT_TRUE(base::Contains(results, kSequenceId)); - const auto& throughput = results[kSequenceId]; - // Frame 3 and 4 are counted. See the sequence in DidCommit comment for - // normal case that expects 2 for both frames_expected and frames_produced. - // - // However, on slow bots, things could be different. - // - Begin frame could be skipped but still counted as expected frames, - // - // e(5,5)b(8)B(0,8)E(8)s(3)S(8)e(8,8)b(11) - // B(8,11)E(11)ts(4)S(11)e(11,11)P(3)e(14,14)P(4) - // - // B(0, 8) and B(8, 11) make frame_expected to be 4, more than 2 expected - // by test. - // - // - Finish before frame 4 is presented in multi-thread mode. - // - // e(3,3)b(4)B(0,4)E(4)s(2)e(4,4)b(6)B(4,6)E(6)s(3)S(4)e(6,6) - // P(2)e(7,7)P(3) - // - // Only P(3) is counted thus frames_produced is 1. - EXPECT_GE(throughput.frames_expected, 2u); - EXPECT_GE(throughput.frames_produced, 1u); EndTest(); } @@ -8931,5 +8913,112 @@ class LayerTreeHostCustomThrougputTrackerTest : public LayerTreeHostTest { SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostCustomThrougputTrackerTest); +// Confirm that DelegatedInkMetadata set on the LTH propagates to the +// CompositorFrameMetadata and RenderFrameMetadata, and then both are correctly +// reset when another frame is drawn without DelegatedInkMetadata. +class LayerTreeHostTestDelegatedInkMetadataOnAndOff + : public LayerTreeHostTest, + public RenderFrameMetadataObserver { + public: + // Provides a wrapper which can be passed to LayerTreeHost, but just forwards + // to the test class. + class ForwardingRenderFrameMetadataObserver + : public RenderFrameMetadataObserver { + public: + explicit ForwardingRenderFrameMetadataObserver( + RenderFrameMetadataObserver* target) + : target_(target) {} + + // RenderFrameMetadataObserver implementation. + void BindToCurrentThread() override { target_->BindToCurrentThread(); } + void OnRenderFrameSubmission( + const RenderFrameMetadata& render_frame_metadata, + viz::CompositorFrameMetadata* compositor_frame_metadata, + bool force_send) override { + target_->OnRenderFrameSubmission(render_frame_metadata, + compositor_frame_metadata, force_send); + } + + private: + RenderFrameMetadataObserver* target_ = nullptr; + }; + + void BeginTest() override { + // Set up a basic render frame observer for the LTH/LTHI to forward to. + layer_tree_host()->SetRenderFrameObserver( + std::make_unique<ForwardingRenderFrameMetadataObserver>(this)); + + // Setting up a basic frame that can be redrawn. + layer_tree_host()->SetViewportRectAndScale(gfx::Rect(10, 10), 1.f, + viz::LocalSurfaceIdAllocation()); + layer_tree_host()->root_layer()->SetBounds(gfx::Size(10, 10)); + layer_ = FakePictureLayer::Create(&client_); + layer_tree_host()->root_layer()->AddChild(layer_); + client_.set_bounds(layer_->bounds()); + + // Values chosen arbitrarily + SkColor color = SK_ColorDKGRAY; + double diameter = 1.000002; + gfx::PointF point = gfx::PointF(135, 45); + gfx::RectF area = gfx::RectF(173, 438); + base::TimeTicks timestamp = base::TimeTicks::Now(); + + expected_metadata_ = + viz::DelegatedInkMetadata(point, diameter, color, timestamp, area); + layer_tree_host()->SetDelegatedInkMetadata( + std::make_unique<viz::DelegatedInkMetadata>( + expected_metadata_.value())); + } + + void DidCommitAndDrawFrame() override { + // Cause a redraw to occur. + layer_->SetNeedsDisplay(); + } + + void DrawLayersOnThread(LayerTreeHostImpl* impl) override { + if (expected_metadata_.has_value()) { + // Now try again with no metadata to confirm everything is cleared out. + expected_metadata_.reset(); + } + } + + void ExpectMetadata(bool had_delegated_ink_metadata, + viz::DelegatedInkMetadata* actual_metadata) { + if (expected_metadata_.has_value()) { + EXPECT_TRUE(had_delegated_ink_metadata); + EXPECT_TRUE(actual_metadata); + EXPECT_EQ(expected_metadata_->point(), actual_metadata->point()); + EXPECT_EQ(expected_metadata_->color(), actual_metadata->color()); + EXPECT_EQ(expected_metadata_->diameter(), actual_metadata->diameter()); + EXPECT_EQ(expected_metadata_->presentation_area(), + actual_metadata->presentation_area()); + EXPECT_EQ(expected_metadata_->timestamp(), actual_metadata->timestamp()); + } else { + EXPECT_FALSE(had_delegated_ink_metadata); + EXPECT_FALSE(actual_metadata); + EndTest(); + } + } + + void AfterTest() override {} + + // RenderFrameMetadataObserver implementation. + void BindToCurrentThread() override {} + void OnRenderFrameSubmission( + const RenderFrameMetadata& render_frame_metadata, + viz::CompositorFrameMetadata* compositor_frame_metadata, + bool force_send) override { + ExpectMetadata(render_frame_metadata.has_delegated_ink_metadata, + compositor_frame_metadata->delegated_ink_metadata.get()); + } + + private: + base::Optional<viz::DelegatedInkMetadata> expected_metadata_; + FakeContentLayerClient client_; + scoped_refptr<Layer> layer_; +}; + +SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestDelegatedInkMetadataOnAndOff); + } // namespace } // namespace cc diff --git a/chromium/cc/trees/layer_tree_host_unittest_copyrequest.cc b/chromium/cc/trees/layer_tree_host_unittest_copyrequest.cc index bb64e9ef4a4..7a188881491 100644 --- a/chromium/cc/trees/layer_tree_host_unittest_copyrequest.cc +++ b/chromium/cc/trees/layer_tree_host_unittest_copyrequest.cc @@ -10,6 +10,7 @@ #include "base/threading/thread_task_runner_handle.h" #include "base/time/time.h" #include "cc/layers/effect_tree_layer_list_iterator.h" +#include "cc/test/cc_test_suite.h" #include "cc/test/fake_content_layer_client.h" #include "cc/test/fake_picture_layer.h" #include "cc/test/layer_tree_test.h" @@ -309,13 +310,19 @@ class LayerTreeHostCopyRequestTestLayerDestroyed base::BindOnce( &LayerTreeHostCopyRequestTestLayerDestroyed::CopyOutputCallback, base::Unretained(this)))); + // We expect that the RequestCopyOfOutput won't yet return results until + // the main is destroyed. So we RunUntilIdle to ensure no PostTask is + // currently queued to return the result. + CCTestSuite::RunUntilIdle(); EXPECT_EQ(0, callback_count_); // Destroy the main thread layer right away. main_destroyed_->RemoveFromParent(); main_destroyed_.reset(); - // Should callback with a NULL bitmap. + // Should callback with a NULL bitmap, result will be in a PostTask so + // RunUntilIdle(). + CCTestSuite::RunUntilIdle(); EXPECT_EQ(1, callback_count_); // Prevent drawing so we can't make a copy of the impl_destroyed layer. @@ -350,7 +357,6 @@ class LayerTreeHostCopyRequestTestLayerDestroyed } void CopyOutputCallback(std::unique_ptr<viz::CopyOutputResult> result) { - EXPECT_TRUE(layer_tree_host()->GetTaskRunnerProvider()->IsMainThread()); EXPECT_TRUE(result->IsEmpty()); ++callback_count_; } diff --git a/chromium/cc/trees/layer_tree_host_unittest_scroll.cc b/chromium/cc/trees/layer_tree_host_unittest_scroll.cc index a5f7b6e522a..3a95b182bf0 100644 --- a/chromium/cc/trees/layer_tree_host_unittest_scroll.cc +++ b/chromium/cc/trees/layer_tree_host_unittest_scroll.cc @@ -10,8 +10,10 @@ #include "base/single_thread_task_runner.h" #include "base/stl_util.h" #include "base/threading/thread_task_runner_handle.h" +#include "build/build_config.h" #include "cc/animation/animation_host.h" #include "cc/base/completion_event.h" +#include "cc/base/features.h" #include "cc/input/main_thread_scrolling_reason.h" #include "cc/input/scroll_elasticity_helper.h" #include "cc/layers/layer.h" @@ -42,11 +44,14 @@ using ::testing::Mock; namespace cc { namespace { -std::unique_ptr<ScrollState> BeginState(const gfx::Point& point) { +std::unique_ptr<ScrollState> BeginState(const gfx::Point& point, + const gfx::Vector2dF& delta_hint) { ScrollStateData scroll_state_data; scroll_state_data.is_beginning = true; scroll_state_data.position_x = point.x(); scroll_state_data.position_y = point.y(); + scroll_state_data.delta_x_hint = delta_hint.x(); + scroll_state_data.delta_y_hint = delta_hint.y(); std::unique_ptr<ScrollState> scroll_state(new ScrollState(scroll_state_data)); return scroll_state; } @@ -685,8 +690,9 @@ class LayerTreeHostScrollTestCaseWithChild : public LayerTreeHostScrollTest { gfx::Point scroll_point = gfx::ToCeiledPoint( gfx::PointF(-0.5f, -0.5f) + GetTransformNode(expected_scroll_layer_impl)->post_translation); - InputHandler::ScrollStatus status = impl->ScrollBegin( - BeginState(scroll_point).get(), ui::ScrollInputType::kTouchscreen); + InputHandler::ScrollStatus status = + impl->ScrollBegin(BeginState(scroll_point, scroll_amount_).get(), + ui::ScrollInputType::kTouchscreen); EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); impl->ScrollUpdate(UpdateState(gfx::Point(), scroll_amount_).get()); auto* scrolling_node = impl->CurrentlyScrollingNode(); @@ -708,8 +714,9 @@ class LayerTreeHostScrollTestCaseWithChild : public LayerTreeHostScrollTest { gfx::Point scroll_point = gfx::ToCeiledPoint( gfx::PointF(0.5f, 0.5f) + GetTransformNode(expected_scroll_layer_impl)->post_translation); - InputHandler::ScrollStatus status = impl->ScrollBegin( - BeginState(scroll_point).get(), ui::ScrollInputType::kWheel); + InputHandler::ScrollStatus status = + impl->ScrollBegin(BeginState(scroll_point, scroll_amount_).get(), + ui::ScrollInputType::kWheel); EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); impl->ScrollUpdate(UpdateState(gfx::Point(), scroll_amount_).get()); impl->ScrollEnd(); @@ -1058,6 +1065,116 @@ class LayerTreeHostScrollTestImplOnlyScroll : public LayerTreeHostScrollTest { // This tests scrolling on the impl side which is only possible with a thread. MULTI_THREAD_TEST_F(LayerTreeHostScrollTestImplOnlyScroll); +// TODO(crbug.com/574283): Mac currently doesn't support smooth scrolling wheel +// events. +#if !defined(OS_MACOSX) +// This test simulates scrolling on the impl thread such that it starts a scroll +// animation. It ensures that RequestScrollAnimationEndNotification() correctly +// notifies the callback after the animation ends. +class SmoothScrollAnimationEndNotification : public LayerTreeHostScrollTest { + public: + SmoothScrollAnimationEndNotification() = default; + + void InitializeSettings(LayerTreeSettings* settings) override { + LayerTreeHostScrollTest::InitializeSettings(settings); + settings->enable_smooth_scroll = true; + } + + void SetupTree() override { + LayerTreeHostScrollTest::SetupTree(); + + Layer* root_layer = layer_tree_host()->root_layer(); + Layer* root_scroll_layer = + layer_tree_host()->OuterViewportScrollLayerForTesting(); + + child_layer_ = Layer::Create(); + child_layer_->SetElementId( + LayerIdToElementIdForTesting(child_layer_->id())); + child_layer_->SetBounds(gfx::Size(110, 110)); + + child_layer_->SetIsDrawable(true); + child_layer_->SetHitTestable(true); + child_layer_->SetElementId( + LayerIdToElementIdForTesting(child_layer_->id())); + child_layer_->SetBounds(root_scroll_layer->bounds()); + root_layer->AddChild(child_layer_); + + CopyProperties(root_scroll_layer, child_layer_.get()); + CreateTransformNode(child_layer_.get()); + CreateScrollNode(child_layer_.get(), root_layer->bounds()); + } + + void BeginTest() override { PostSetNeedsCommitToMainThread(); } + + void WillCommit() override { + // Keep the test committing (otherwise the early out for no update + // will stall the test). + if (layer_tree_host()->SourceFrameNumber() < 2) { + layer_tree_host()->SetNeedsCommit(); + } + } + + void DidActivateTreeOnThread(LayerTreeHostImpl* host_impl) override { + if (host_impl->active_tree()->source_frame_number() < 0) + return; + + if (host_impl->active_tree()->source_frame_number() == 0) { + const gfx::Point scroll_point(10, 10); + const gfx::Vector2dF scroll_amount(350, -350); + auto scroll_state = BeginState(scroll_point, scroll_amount); + scroll_state->data()->delta_granularity = + ui::ScrollGranularity::kScrollByPixel; + InputHandler::ScrollStatus status = host_impl->ScrollBegin( + scroll_state.get(), ui::ScrollInputType::kWheel); + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + scroll_state = UpdateState(scroll_point, scroll_amount); + scroll_state->data()->delta_granularity = + ui::ScrollGranularity::kScrollByPixel; + host_impl->ScrollUpdate(scroll_state.get()); + + EXPECT_TRUE( + !!host_impl->mutator_host()->ImplOnlyScrollAnimatingElement()); + } else if (!scroll_end_requested_) { + host_impl->ScrollEnd(false); + scroll_end_requested_ = true; + } + PostSetNeedsCommitToMainThread(); + } + + void UpdateLayerTreeHost() override { + if (scroll_animation_started_) + return; + + if (layer_tree_host()->HasCompositorDrivenScrollAnimationForTesting()) { + scroll_animation_started_ = true; + layer_tree_host()->RequestScrollAnimationEndNotification( + base::BindOnce(&SmoothScrollAnimationEndNotification::OnScrollEnd, + base::Unretained(this))); + } + } + + void AfterTest() override { + EXPECT_TRUE(scroll_end_requested_); + EXPECT_TRUE(scroll_animation_started_); + EXPECT_TRUE(scroll_animation_ended_); + } + + private: + void OnScrollEnd() { + scroll_animation_ended_ = true; + EndTest(); + } + + scoped_refptr<Layer> child_layer_; + + bool scroll_end_requested_ = false; + bool scroll_animation_started_ = false; + bool scroll_animation_ended_ = false; +}; + +MULTI_THREAD_TEST_F(SmoothScrollAnimationEndNotification); +#endif // !defined(OS_MACOSX) + void DoGestureScroll(LayerTreeHostImpl* host_impl, const scoped_refptr<Layer>& scroller, gfx::Vector2dF offset) { @@ -1354,12 +1471,13 @@ class LayerTreeHostScrollTestScrollZeroMaxScrollOffset void SetupTree() override { LayerTreeHostScrollTest::SetupTree(); - // Add a sub-scroller to test TryScroll against. The outer viewport scroll + // Add a sub-scroller to test ScrollBegin against. The outer viewport scroll // will be latched to for scrolling even if it doesn't have any scroll // extent in the given direction to support overscroll actions. scroller_ = Layer::Create(); scroller_->SetIsDrawable(true); scroller_->SetHitTestable(true); + scroller_->SetBounds(gfx::Size(200, 200)); scroller_->SetElementId(LayerIdToElementIdForTesting(scroller_->id())); CopyProperties(layer_tree_host()->OuterViewportScrollLayerForTesting(), scroller_.get()); @@ -1369,61 +1487,91 @@ class LayerTreeHostScrollTestScrollZeroMaxScrollOffset layer_tree_host()->root_layer()->AddChild(scroller_.get()); } - void BeginTest() override { PostSetNeedsCommitToMainThread(); } + void BeginTest() override { NextStep(); } + + void NextStep() { + if (TestEnded()) + return; + + ++cur_step_; - void UpdateLayerTreeHost() override { ScrollTree& scroll_tree = layer_tree_host()->property_trees()->scroll_tree; ScrollNode* scroll_node = scroll_tree.Node(scroller_->scroll_tree_index()); - switch (layer_tree_host()->SourceFrameNumber()) { - case 0: + switch (cur_step_) { + case 1: // Set max_scroll_offset = (100, 100). scroll_node->bounds = scroll_node->container_bounds; scroll_node->bounds.Enlarge(100, 100); break; - case 1: + case 2: // Set max_scroll_offset = (0, 0). scroll_node->bounds = scroll_node->container_bounds; break; - case 2: + case 3: // Set max_scroll_offset = (-1, -1). scroll_node->bounds = gfx::Size(); break; } + + layer_tree_host()->SetNeedsCommit(); } void DrawLayersOnThread(LayerTreeHostImpl* impl) override { + if (TestEnded()) + return; + ScrollTree& scroll_tree = impl->active_tree()->property_trees()->scroll_tree; ScrollNode* scroll_node = scroll_tree.Node(scroller_->scroll_tree_index()); + + ScrollStateData scroll_state_data; + scroll_state_data.is_beginning = true; + // The position has to be at (0, 0) since the viewport in this test has + // bounds (1, 1). + scroll_state_data.position_x = 0; + scroll_state_data.position_y = 0; + scroll_state_data.delta_x_hint = 10; + scroll_state_data.delta_y_hint = 10; + scroll_state_data.is_direct_manipulation = true; + + ScrollState scroll_state(scroll_state_data); InputHandler::ScrollStatus status = - impl->TryScroll(scroll_tree, scroll_node); - switch (impl->active_tree()->source_frame_number()) { - case 0: - EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread) - << "In Frame 0"; - EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain, - status.main_thread_scrolling_reasons) - << "In Frame 0"; - PostSetNeedsCommitToMainThread(); - break; + impl->ScrollBegin(&scroll_state, ui::ScrollInputType::kTouchscreen); + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread) + << "In Frame " << impl->active_tree()->source_frame_number(); + + switch (cur_step_) { case 1: - EXPECT_EQ(InputHandler::SCROLL_IGNORED, status.thread) << "In Frame 1"; - EXPECT_EQ(MainThreadScrollingReason::kNotScrollable, - status.main_thread_scrolling_reasons) - << "In Frame 1"; - PostSetNeedsCommitToMainThread(); + // Since the scroller has scroll extend and is scrollable, we should + // have targeted it. + EXPECT_EQ(scroll_node, impl->CurrentlyScrollingNode()) << "In Frame 0"; break; case 2: - EXPECT_EQ(InputHandler::SCROLL_IGNORED, status.thread) << "In Frame 2"; - EXPECT_EQ(MainThreadScrollingReason::kNotScrollable, - status.main_thread_scrolling_reasons) + // Since the max_scroll_offset is (0, 0) - we shouldn't target it and + // we should instead bubble up to the viewport. + EXPECT_EQ(impl->OuterViewportScrollNode(), + impl->CurrentlyScrollingNode()) + << "In Frame 1"; + break; + case 3: + // Since the max_scroll_offset is (-1, -1) - we shouldn't target it and + // we should instead bubble up to the viewport. + EXPECT_EQ(impl->OuterViewportScrollNode(), + impl->CurrentlyScrollingNode()) << "In Frame 2"; EndTest(); break; } + impl->ScrollEnd(); + MainThreadTaskRunner()->PostTask( + FROM_HERE, + base::BindOnce( + &LayerTreeHostScrollTestScrollZeroMaxScrollOffset::NextStep, + base::Unretained(this))); } private: + int cur_step_ = 0; scoped_refptr<Layer> scroller_; }; @@ -1453,14 +1601,23 @@ class LayerTreeHostScrollTestScrollNonDrawnLayer // checking whether the screen space point is inside the non-fast // scrollable region. InputHandler::ScrollStatus status = impl->ScrollBegin( - BeginState(gfx::Point(0, 0)).get(), ui::ScrollInputType::kTouchscreen); - EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); - EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion, - status.main_thread_scrolling_reasons); + BeginState(gfx::Point(0, 0), gfx::Vector2dF(0, 1)).get(), + ui::ScrollInputType::kTouchscreen); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_TRUE(status.needs_main_thread_hit_test); + impl->ScrollEnd(); + } else { + EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion, + status.main_thread_scrolling_reasons); + } - status = impl->ScrollBegin(BeginState(gfx::Point(21, 21)).get(), - ui::ScrollInputType::kTouchscreen); + status = impl->ScrollBegin( + BeginState(gfx::Point(21, 21), gfx::Vector2dF(0, 1)).get(), + ui::ScrollInputType::kTouchscreen); EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_FALSE(status.needs_main_thread_hit_test); EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain, status.main_thread_scrolling_reasons); @@ -1481,36 +1638,88 @@ class LayerTreeHostScrollTestImplScrollUnderMainThreadScrollingParent void SetupTree() override { LayerTreeHostScrollTest::SetupTree(); - GetScrollNode(layer_tree_host()->InnerViewportScrollLayerForTesting()) + GetScrollNode(layer_tree_host()->OuterViewportScrollLayerForTesting()) ->main_thread_scrolling_reasons = - MainThreadScrollingReason::kScrollbarScrolling; + MainThreadScrollingReason::kThreadedScrollingDisabled; + + scroller_ = Layer::Create(); + scroller_->SetIsDrawable(true); + scroller_->SetHitTestable(true); + scroller_->SetBounds(gfx::Size(200, 200)); + scroller_->SetElementId(LayerIdToElementIdForTesting(scroller_->id())); + CopyProperties(layer_tree_host()->OuterViewportScrollLayerForTesting(), + scroller_.get()); + CreateTransformNode(scroller_.get()); + CreateScrollNode(scroller_.get(), + layer_tree_host()->root_layer()->bounds()); + layer_tree_host()->root_layer()->AddChild(scroller_.get()); } void DrawLayersOnThread(LayerTreeHostImpl* impl) override { - LayerImpl* inner_scroll_layer = - impl->active_tree()->InnerViewportScrollLayerForTesting(); - LayerImpl* outer_scroll_layer = - impl->active_tree()->OuterViewportScrollLayerForTesting(); - ScrollTree& scroll_tree = impl->active_tree()->property_trees()->scroll_tree; - ScrollNode* inner_scroll_node = - scroll_tree.Node(inner_scroll_layer->scroll_tree_index()); - ScrollNode* outer_scroll_node = - scroll_tree.Node(outer_scroll_layer->scroll_tree_index()); + ScrollNode* scroller_scroll_node = + scroll_tree.Node(scroller_->scroll_tree_index()); + + ScrollStateData scroll_state_data; + scroll_state_data.is_beginning = true; + // To hit the scroller, the position has to be at (0, 0) since the viewport + // in this test has bounds (1, 1) and would otherwise clip the hit test. + scroll_state_data.position_x = 0; + scroll_state_data.position_y = 0; + scroll_state_data.delta_x_hint = 10; + scroll_state_data.delta_y_hint = 10; + scroll_state_data.is_direct_manipulation = true; + ScrollState scroll_state(scroll_state_data); + + // Scroll hitting the scroller layer. + { + InputHandler::ScrollStatus status = + impl->ScrollBegin(&scroll_state, ui::ScrollInputType::kTouchscreen); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_EQ(impl->CurrentlyScrollingNode(), scroller_scroll_node); + EXPECT_FALSE(status.needs_main_thread_hit_test); + } else { + // Despite the fact that we hit the scroller, which has no main thread + // scrolling reason, we still must fallback to main thread scrolling due + // to the fact that it has a main thread scrolling ancestor. + EXPECT_EQ(impl->CurrentlyScrollingNode(), nullptr); + EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kThreadedScrollingDisabled, + status.main_thread_scrolling_reasons); + } + impl->ScrollEnd(); + } - InputHandler::ScrollStatus status = - impl->TryScroll(scroll_tree, inner_scroll_node); - EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); - EXPECT_EQ(MainThreadScrollingReason::kScrollbarScrolling, - status.main_thread_scrolling_reasons); + // Scroll hitting the viewport layer. + { + // A hit test outside the viewport should fallback to scrolling the + // viewport. + scroll_state.data()->position_y = 1000; + + InputHandler::ScrollStatus status = + impl->ScrollBegin(&scroll_state, ui::ScrollInputType::kTouchscreen); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_FALSE(status.needs_main_thread_hit_test); + EXPECT_EQ(impl->CurrentlyScrollingNode(), + impl->OuterViewportScrollNode()); + } else { + // Since the viewport has a main thread scrolling reason, this + // too should fallback to the main thread. + EXPECT_EQ(impl->CurrentlyScrollingNode(), nullptr); + EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kThreadedScrollingDisabled, + status.main_thread_scrolling_reasons); + } + impl->ScrollEnd(); + } - status = impl->TryScroll(scroll_tree, outer_scroll_node); - EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); - EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain, - status.main_thread_scrolling_reasons); EndTest(); } + + private: + scoped_refptr<Layer> scroller_; }; SINGLE_AND_MULTI_THREAD_TEST_F( @@ -2422,27 +2631,28 @@ class NonScrollingNonFastScrollableRegion : public LayerTreeHostScrollTest { void SetupTree() override { SetInitialRootBounds(gfx::Size(800, 600)); LayerTreeHostScrollTest::SetupTree(); - Layer* root = layer_tree_host()->root_layer(); - fake_content_layer_client_.set_bounds(root->bounds()); + Layer* outer_scroll = + layer_tree_host()->OuterViewportScrollLayerForTesting(); + fake_content_layer_client_.set_bounds(outer_scroll->bounds()); bottom_ = FakePictureLayer::Create(&fake_content_layer_client_); bottom_->SetElementId(LayerIdToElementIdForTesting(bottom_->id())); bottom_->SetBounds(gfx::Size(100, 100)); bottom_->SetNonFastScrollableRegion(Region(gfx::Rect(50, 50, 50, 50))); bottom_->SetHitTestable(true); - CopyProperties(root, bottom_.get()); - root->AddChild(bottom_); + CopyProperties(outer_scroll, bottom_.get()); + outer_scroll->AddChild(bottom_); middle_scrollable_ = FakePictureLayer::Create(&fake_content_layer_client_); middle_scrollable_->SetElementId( LayerIdToElementIdForTesting(middle_scrollable_->id())); - middle_scrollable_->SetBounds(gfx::Size(100, 100)); + middle_scrollable_->SetBounds(gfx::Size(100, 200)); middle_scrollable_->SetIsDrawable(true); middle_scrollable_->SetHitTestable(true); CopyProperties(bottom_.get(), middle_scrollable_.get()); CreateTransformNode(middle_scrollable_.get()); - CreateScrollNode(middle_scrollable_.get(), gfx::Size(100, 200)); - root->AddChild(middle_scrollable_); + CreateScrollNode(middle_scrollable_.get(), gfx::Size(100, 100)); + outer_scroll->AddChild(middle_scrollable_); top_ = FakePictureLayer::Create(&fake_content_layer_client_); top_->SetElementId(LayerIdToElementIdForTesting(top_->id())); @@ -2450,7 +2660,7 @@ class NonScrollingNonFastScrollableRegion : public LayerTreeHostScrollTest { top_->SetNonFastScrollableRegion(Region(gfx::Rect(0, 0, 50, 50))); top_->SetHitTestable(true); CopyProperties(middle_scrollable_.get(), top_.get()); - root->AddChild(top_); + outer_scroll->AddChild(top_); } void BeginTest() override { PostSetNeedsCommitToMainThread(); } @@ -2459,35 +2669,70 @@ class NonScrollingNonFastScrollableRegion : public LayerTreeHostScrollTest { if (TestEnded()) return; - // The top-left hit should immediately hit the top layer's non-fast region - // which forces main-thread scrolling. - auto top_left_status = - impl->ScrollBegin(BeginState(gfx::Point(20, 20)).get(), - ui::ScrollInputType::kTouchscreen); - EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, top_left_status.thread); - EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion, - top_left_status.main_thread_scrolling_reasons); - - // The top-right hit should hit the top layer but not the non-fast region so - // the scroll should continue to scroll on the impl. - InputHandler::ScrollStatus top_right_status = - impl->ScrollBegin(BeginState(gfx::Point(80, 20)).get(), - ui::ScrollInputType::kTouchscreen); - EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, top_right_status.thread); - EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain, - top_right_status.main_thread_scrolling_reasons); - impl->ScrollEnd(); + ScrollNode* scroll_node = + impl->active_tree()->property_trees()->scroll_tree.Node( + middle_scrollable_->scroll_tree_index()); - // The bottom-right should hit the bottom layer's non-fast region. Though - // the middle layer is a composited scroller and is hit first, we cannot do - // a fast scroll because an ancestor on the scroll chain has hit a non-fast - // region. - InputHandler::ScrollStatus bottom_right_status = - impl->ScrollBegin(BeginState(gfx::Point(80, 80)).get(), - ui::ScrollInputType::kTouchscreen); - EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, bottom_right_status.thread); - EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion, - bottom_right_status.main_thread_scrolling_reasons); + // The top-left hit should immediately hit the top layer's non-fast region. + { + auto status = impl->ScrollBegin( + BeginState(gfx::Point(20, 20), gfx::Vector2dF(0, 1)).get(), + ui::ScrollInputType::kTouchscreen); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + // Hitting a non fast region should request a hit test from the main + // thread. + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_TRUE(status.needs_main_thread_hit_test); + impl->ScrollEnd(); + } else { + // Prior to scroll unification, this forces scrolling to the main + // thread. + EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion, + status.main_thread_scrolling_reasons); + } + } + + // The top-right hit should hit the top layer but not the non-fast region + // so the scroll can be handled without involving the main thread. + { + InputHandler::ScrollStatus status = impl->ScrollBegin( + BeginState(gfx::Point(80, 20), gfx::Vector2dF(0, 1)).get(), + ui::ScrollInputType::kTouchscreen); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_FALSE(status.needs_main_thread_hit_test); + } else { + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain, + status.main_thread_scrolling_reasons); + } + EXPECT_EQ(scroll_node, impl->CurrentlyScrollingNode()); + impl->ScrollEnd(); + } + + // The bottom-right should hit the bottom layer's non-fast region. + { + InputHandler::ScrollStatus status = impl->ScrollBegin( + BeginState(gfx::Point(80, 80), gfx::Vector2dF(0, 1)).get(), + ui::ScrollInputType::kTouchscreen); + if (base::FeatureList::IsEnabled(features::kScrollUnification)) { + // Even though the point intersects a non-fast region, the first hit + // layer is scrollable from the compositor thread so no need to involve + // the main thread. + EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + EXPECT_FALSE(status.needs_main_thread_hit_test); + EXPECT_EQ(scroll_node, impl->CurrentlyScrollingNode()); + impl->ScrollEnd(); + } else { + // Though the middle layer is a composited scroller and is hit first, we + // cannot do a fast scroll because an ancestor on the scroll chain has + // hit a non-fast region. + EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread); + EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion, + status.main_thread_scrolling_reasons); + } + } EndTest(); } @@ -2501,5 +2746,70 @@ class NonScrollingNonFastScrollableRegion : public LayerTreeHostScrollTest { SINGLE_THREAD_TEST_F(NonScrollingNonFastScrollableRegion); +// This test verifies that scrolling in non layer list mode (used by UI +// compositor) is always "compositor scrolled", i.e. property trees are mutated +// and the updated layers redrawn. This test intentionally doesn't inherit +// from LayerTreeHostScrollTest since that enables LayerLists. +class UnifiedScrollingRepaintOnScroll : public LayerTreeTest { + public: + UnifiedScrollingRepaintOnScroll() { + scoped_feature_list.InitAndEnableFeature(features::kScrollUnification); + } + + void BeginTest() override { PostSetNeedsCommitToMainThread(); } + + void SetupTree() override { + LayerTreeTest::SetupTree(); + + layer_ = FakePictureLayer::Create(&client_); + layer_->SetScrollable(gfx::Size(10, 10)); + layer_->SetBounds(gfx::Size(100, 100)); + layer_->SetIsDrawable(true); + layer_->SetHitTestable(true); + layer_->SetElementId(LayerIdToElementIdForTesting(layer_->id())); + client_.set_bounds(layer_->bounds()); + layer_tree_host()->root_layer()->AddChild(layer_); + } + + void DrawLayersOnThread(LayerTreeHostImpl* impl) override { + if (is_done_) + return; + is_done_ = true; + EndTest(); + + TransformTree& transform_tree = + impl->active_tree()->property_trees()->transform_tree; + ASSERT_FALSE(transform_tree.needs_update()); + + // Perform a scroll over our FakePictureLayer. + { + InputHandler::ScrollStatus status = impl->ScrollBegin( + BeginState(gfx::Point(0, 0), gfx::Vector2dF(0, 10)).get(), + ui::ScrollInputType::kTouchscreen); + + ASSERT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread); + ASSERT_EQ(layer_->scroll_tree_index(), + impl->CurrentlyScrollingNode()->id); + + impl->ScrollUpdate( + UpdateState(gfx::Point(), gfx::Vector2dF(0, 10)).get()); + impl->ScrollEnd(); + } + + // All scrolling in non-layer-list mode (i.e. UI compositor) should be + // "compositor" scrolling so it should mutate the property tree and redraw, + // rather than relying on an update from the main thread. + ASSERT_TRUE(transform_tree.needs_update()); + } + + private: + bool is_done_ = false; + scoped_refptr<Layer> layer_; + FakeContentLayerClient client_; + base::test::ScopedFeatureList scoped_feature_list; +}; + +MULTI_THREAD_TEST_F(UnifiedScrollingRepaintOnScroll); + } // namespace } // namespace cc diff --git a/chromium/cc/trees/layer_tree_impl.cc b/chromium/cc/trees/layer_tree_impl.cc index 600c987976c..7ab7bf797ef 100644 --- a/chromium/cc/trees/layer_tree_impl.cc +++ b/chromium/cc/trees/layer_tree_impl.cc @@ -10,7 +10,9 @@ #include <algorithm> #include <iterator> #include <limits> +#include <memory> #include <set> +#include <utility> #include "base/containers/adapters.h" #include "base/debug/crash_logging.h" @@ -24,6 +26,7 @@ #include "base/trace_event/trace_event.h" #include "base/trace_event/traced_value.h" #include "cc/base/devtools_instrumentation.h" +#include "cc/base/features.h" #include "cc/base/histograms.h" #include "cc/base/math_util.h" #include "cc/base/synced_property.h" @@ -209,17 +212,38 @@ void LayerTreeImpl::DidUpdateScrollOffset(ElementId id) { return; } + // This bit controls whether we'll update the transform node based on a + // changed scroll offset. If scroll unification is off, we always do this + // because the scroll handling code will only invoke a scroll update on nodes + // that can compositor scroll. However, with scroll unification, we can + // mutate scroll nodes which have main thread scrolling reasons, or aren't + // backed by a layer at all. In those cases, we don't want to produce any + // immediate changes in the compositor, we want the scroll to propagate + // through Blink in a commit and have Blink update properties, paint, + // compositing, etc. Thus, we avoid mutating the transform tree in this case. + // TODO(bokan): We SetNeedsCommit in LTHI when a scroll happens but in a + // normal compositor scroll there isn't much urgency for a commit to be + // scheduled. We should look into what we can do to make sure this is + // proritized accordingly. https://crbug.com/1082618. + bool can_realize_scroll_on_compositor = + !base::FeatureList::IsEnabled(features::kScrollUnification) || + (scroll_node->is_composited && + !scroll_node->main_thread_scrolling_reasons); + DCHECK(scroll_node->transform_id != TransformTree::kInvalidNodeId); TransformTree& transform_tree = property_trees()->transform_tree; auto* transform_node = transform_tree.Node(scroll_node->transform_id); - if (transform_node->scroll_offset != scroll_tree.current_scroll_offset(id)) { - transform_node->scroll_offset = scroll_tree.current_scroll_offset(id); - transform_node->needs_local_transform_update = true; - transform_tree.set_needs_update(true); + if (can_realize_scroll_on_compositor) { + if (transform_node->scroll_offset != + scroll_tree.current_scroll_offset(id)) { + transform_node->scroll_offset = scroll_tree.current_scroll_offset(id); + transform_node->needs_local_transform_update = true; + transform_tree.set_needs_update(true); + } + transform_node->transform_changed = true; + property_trees()->changed = true; + set_needs_update_draw_properties(); } - transform_node->transform_changed = true; - property_trees()->changed = true; - set_needs_update_draw_properties(); if (IsActiveTree()) { // Ensure the other trees are kept in sync. @@ -419,6 +443,13 @@ void LayerTreeImpl::UpdateViewportContainerSizes() { OuterViewportScrollNode()->container_bounds.height() + scaled_bounds_delta.y(); outer_clip_node->clip.set_height(adjusted_container_height); + + // Expand all clips between the outer viewport and the inner viewport. + auto* outer_ancestor = property_trees->clip_tree.parent(outer_clip_node); + while (outer_ancestor && outer_ancestor->id != ClipTree::kRootNodeId) { + outer_ancestor->clip.Union(outer_clip_node->clip); + outer_ancestor = property_trees->clip_tree.parent(outer_ancestor); + } } anchor.ResetViewportToAnchoredPosition(); @@ -477,6 +508,16 @@ OwnedLayerImplList LayerTreeImpl::DetachLayersKeepingRootLayerForTesting() { } void LayerTreeImpl::SetPropertyTrees(PropertyTrees* property_trees) { + // Updating the scroll tree shouldn't clobber the currently scrolling node so + // stash it and restore it at the end of this method. To maintain the + // current scrolling node we need to use element ids which are stable across + // the property tree update in SetPropertyTrees. + ElementId scrolling_element_id; + if (IsActiveTree()) { + if (ScrollNode* scrolling_node = CurrentlyScrollingNode()) + scrolling_element_id = scrolling_node->element_id; + } + std::vector<std::unique_ptr<RenderSurfaceImpl>> old_render_surfaces; property_trees_.effect_tree.TakeRenderSurfaces(&old_render_surfaces); property_trees_ = *property_trees; @@ -493,6 +534,13 @@ void LayerTreeImpl::SetPropertyTrees(PropertyTrees* property_trees) { // effect tree. if (IsActiveTree()) property_trees_.effect_tree.set_needs_update(true); + + const ScrollNode* scrolling_node = nullptr; + if (scrolling_element_id) { + auto& scroll_tree = property_trees_.scroll_tree; + scrolling_node = scroll_tree.FindNodeFromElementId(scrolling_element_id); + } + SetCurrentlyScrollingNode(scrolling_node); } void LayerTreeImpl::PushPropertyTreesTo(LayerTreeImpl* target_tree) { @@ -508,21 +556,8 @@ void LayerTreeImpl::PushPropertyTreesTo(LayerTreeImpl* target_tree) { target_tree->MoveChangeTrackingToLayers(); } - // To maintain the current scrolling node we need to use element ids which - // are stable across the property tree update in SetPropertyTrees. - ElementId scrolling_element_id; - if (ScrollNode* scrolling_node = target_tree->CurrentlyScrollingNode()) - scrolling_element_id = scrolling_node->element_id; - target_tree->SetPropertyTrees(&property_trees_); - const ScrollNode* scrolling_node = nullptr; - if (scrolling_element_id) { - auto& scroll_tree = target_tree->property_trees()->scroll_tree; - scrolling_node = scroll_tree.FindNodeFromElementId(scrolling_element_id); - } - target_tree->SetCurrentlyScrollingNode(scrolling_node); - std::vector<EventMetrics> events_metrics; events_metrics.swap(events_metrics_from_main_thread_); target_tree->AppendEventsMetricsFromMainThread(std::move(events_metrics)); @@ -618,6 +653,13 @@ void LayerTreeImpl::PushPropertiesTo(LayerTreeImpl* target_tree) { target_tree->HandleScrollbarShowRequestsFromMain(); target_tree->AddPresentationCallbacks(std::move(presentation_callbacks_)); presentation_callbacks_.clear(); + + if (delegated_ink_metadata_) { + TRACE_EVENT_INSTANT1("cc", "Delegated ink metadata pushed to tree", + TRACE_EVENT_SCOPE_THREAD, "point", + delegated_ink_metadata_->point().ToString()); + target_tree->set_delegated_ink_metadata(std::move(delegated_ink_metadata_)); + } } void LayerTreeImpl::HandleTickmarksVisibilityChange() { @@ -1581,12 +1623,8 @@ ImageAnimationController* LayerTreeImpl::image_animation_controller() const { return host_impl_->image_animation_controller(); } -FrameRateCounter* LayerTreeImpl::frame_rate_counter() const { - return host_impl_->fps_counter(); -} - -base::Optional<int> LayerTreeImpl::current_universal_throughput() { - return host_impl_->current_universal_throughput(); +DroppedFrameCounter* LayerTreeImpl::dropped_frame_counter() const { + return host_impl_->dropped_frame_counter(); } MemoryHistory* LayerTreeImpl::memory_history() const { @@ -1937,9 +1975,11 @@ void LayerTreeImpl::UnregisterScrollbar( if (scrollbar_ids.horizontal == Layer::INVALID_ID && scrollbar_ids.vertical == Layer::INVALID_ID) { element_id_to_scrollbar_layer_ids_.erase(scroll_element_id); - if (IsActiveTree()) { - host_impl_->DidUnregisterScrollbarLayer(scroll_element_id); - } + } + + if (IsActiveTree()) { + host_impl_->DidUnregisterScrollbarLayer(scroll_element_id, + scrollbar_layer->orientation()); } } @@ -2256,6 +2296,19 @@ LayerTreeImpl::FindLayersHitByPointInNonFastScrollableRegion( return layers; } +bool LayerTreeImpl::PointHitsNonFastScrollableRegion( + const gfx::PointF& screen_space_point, + const LayerImpl& layer) const { + // We assume the layer has already been hit tested. + DCHECK(PointHitsLayer(&layer, screen_space_point, nullptr)); + + if (layer.non_fast_scrollable_region().IsEmpty()) + return false; + + return PointHitsRegion(screen_space_point, layer.ScreenSpaceTransform(), + layer.non_fast_scrollable_region(), &layer); +} + struct HitTestFramedVisibleScrollableOrTouchableFunctor { bool operator()(LayerImpl* layer) const { return layer->HitTestable() && layer->frame_element_id(); diff --git a/chromium/cc/trees/layer_tree_impl.h b/chromium/cc/trees/layer_tree_impl.h index 5c2f586a0ea..d4583970c74 100644 --- a/chromium/cc/trees/layer_tree_impl.h +++ b/chromium/cc/trees/layer_tree_impl.h @@ -46,7 +46,7 @@ class ContextProvider; namespace cc { class DebugRectHistory; -class FrameRateCounter; +class DroppedFrameCounter; class HeadsUpDisplayLayerImpl; class ImageDecodeCache; class LayerTreeDebugState; @@ -127,8 +127,7 @@ class CC_EXPORT LayerTreeImpl { TileManager* tile_manager() const; ImageDecodeCache* image_decode_cache() const; ImageAnimationController* image_animation_controller() const; - FrameRateCounter* frame_rate_counter() const; - base::Optional<int> current_universal_throughput(); + DroppedFrameCounter* dropped_frame_counter() const; MemoryHistory* memory_history() const; DebugRectHistory* debug_rect_history() const; bool IsActiveTree() const; @@ -591,6 +590,8 @@ class CC_EXPORT LayerTreeImpl { // Return all layers with a hit non-fast scrollable region. std::vector<const LayerImpl*> FindLayersHitByPointInNonFastScrollableRegion( const gfx::PointF& screen_space_point); + bool PointHitsNonFastScrollableRegion(const gfx::PointF& scree_space_point, + const LayerImpl& layer) const; // Returns the ElementId representing a frame's document at the given point. // In cases where cc doesn't have enough information to perform accurate @@ -710,6 +711,18 @@ class CC_EXPORT LayerTreeImpl { return host_impl_->DrawTransform(); } + // These functions are used for plumbing DelegatedInkMetadata from blink + // through the compositor and into viz via a compositor frame. They should + // only be called after the JS API |updateInkTrailStartPoint| has been + // called, which populates the metadata with provided information. + void set_delegated_ink_metadata( + std::unique_ptr<viz::DelegatedInkMetadata> metadata) { + delegated_ink_metadata_ = std::move(metadata); + } + std::unique_ptr<viz::DelegatedInkMetadata> take_delegated_ink_metadata() { + return std::move(delegated_ink_metadata_); + } + protected: float ClampPageScaleFactorToLimits(float page_scale_factor) const; void PushPageScaleFactorAndLimits(const float* page_scale_factor, @@ -861,6 +874,8 @@ class CC_EXPORT LayerTreeImpl { // Event metrics that are reported back from the main thread. std::vector<EventMetrics> events_metrics_from_main_thread_; + + std::unique_ptr<viz::DelegatedInkMetadata> delegated_ink_metadata_; }; } // namespace cc diff --git a/chromium/cc/trees/layer_tree_settings.h b/chromium/cc/trees/layer_tree_settings.h index 95a901678c2..446d46764c5 100644 --- a/chromium/cc/trees/layer_tree_settings.h +++ b/chromium/cc/trees/layer_tree_settings.h @@ -187,10 +187,18 @@ class CC_EXPORT LayerTreeSettings { // Whether experimental de-jelly effect is allowed. bool allow_de_jelly_effect = false; + // Whether the compositor should attempt to sync with the scroll handlers + // before submitting a frame. + bool enable_synchronized_scrolling = true; + #if DCHECK_IS_ON() // Whether to check if any double blur exists. bool log_on_ui_double_background_blur = false; #endif + + // When enabled, enforces new interoperable semantics for 3D transforms. + // See crbug.com/1008483. + bool enable_transform_interop = false; }; class CC_EXPORT LayerListSettings : public LayerTreeSettings { diff --git a/chromium/cc/trees/mutator_host.h b/chromium/cc/trees/mutator_host.h index 1292e0ec826..e3cf671314b 100644 --- a/chromium/cc/trees/mutator_host.h +++ b/chromium/cc/trees/mutator_host.h @@ -160,7 +160,6 @@ class MutatorHost { // the scroller. Otherwise returns an invalid ElementId. virtual ElementId ImplOnlyScrollAnimatingElement() const = 0; - virtual size_t CompositedAnimationsCount() const = 0; virtual size_t MainThreadAnimationsCount() const = 0; virtual bool HasCustomPropertyAnimations() const = 0; virtual bool CurrentFrameHadRAF() const = 0; diff --git a/chromium/cc/trees/occlusion_tracker.h b/chromium/cc/trees/occlusion_tracker.h index d2498676dec..c734beff463 100644 --- a/chromium/cc/trees/occlusion_tracker.h +++ b/chromium/cc/trees/occlusion_tracker.h @@ -59,7 +59,7 @@ class CC_EXPORT OcclusionTracker { protected: struct StackObject { - StackObject() : target(0) {} + StackObject() : target(nullptr) {} explicit StackObject(const RenderSurfaceImpl* target) : target(target) {} const RenderSurfaceImpl* target; SimpleEnclosedRegion occlusion_from_outside_target; diff --git a/chromium/cc/trees/property_tree.cc b/chromium/cc/trees/property_tree.cc index cbdb9d43e73..00ce2f2e65d 100644 --- a/chromium/cc/trees/property_tree.cc +++ b/chromium/cc/trees/property_tree.cc @@ -493,24 +493,27 @@ void TransformTree::UpdateSnapping(TransformNode* node) { // rounded, then what we're after is the scroll delta X, where ST * X = ST'. // I.e., we want a transform that will realize our snap. It follows that // X = ST^-1 * ST'. We cache ST and ST^-1 to make this more efficient. - gfx::Transform rounded = ToScreen(node->id); - rounded.RoundTranslationComponents(); - gfx::Transform delta = FromScreen(node->id); - delta *= rounded; - - DCHECK(delta.IsApproximatelyIdentityOrTranslation(SkDoubleToScalar(1e-4))) + DCHECK_LT(node->id, static_cast<int>(cached_data_.size())); + gfx::Transform& to_screen = cached_data_[node->id].to_screen; + to_screen.RoundTranslationComponents(); + gfx::Transform& from_screen = cached_data_[node->id].from_screen; + gfx::Transform delta = from_screen; + delta *= to_screen; + + constexpr float kTolerance = 1e-4f; + DCHECK(delta.IsApproximatelyIdentityOrTranslation(kTolerance)) << delta.ToString(); gfx::Vector2dF translation = delta.To2dTranslation(); + node->snap_amount = translation; + if (translation.IsZero()) + return; - // Now that we have our delta, we must apply it to each of our combined, - // to/from matrices. - SetToScreen(node->id, rounded); - node->to_parent.Translate(translation.x(), translation.y()); - gfx::Transform from_screen = FromScreen(node->id); from_screen.matrix().postTranslate(-translation.x(), -translation.y(), 0); - SetFromScreen(node->id, from_screen); - node->snap_amount = translation; + node->to_parent.Translate(translation.x(), translation.y()); + // Avoid accumulation of errors in to_parent. + if (node->to_parent.IsApproximatelyIdentityOrIntegerTranslation(kTolerance)) + node->to_parent.RoundTranslationComponents(); } void TransformTree::UpdateTransformChanged(TransformNode* node, @@ -745,6 +748,17 @@ void EffectTree::UpdateHasMaskingChild(EffectNode* node, } } +void EffectTree::UpdateOnlyDrawsVisibleContent(EffectNode* node, + EffectNode* parent_node) { + node->only_draws_visible_content = !node->has_copy_request; + if (parent_node) + node->only_draws_visible_content &= parent_node->only_draws_visible_content; + if (!node->backdrop_filters.IsEmpty()) { + node->only_draws_visible_content &= + !node->backdrop_filters.HasFilterOfType(FilterOperation::ZOOM); + } +} + void EffectTree::UpdateSurfaceContentsScale(EffectNode* effect_node) { if (!effect_node->HasRenderSurface()) { effect_node->surface_contents_scale = gfx::Vector2dF(1.0f, 1.0f); @@ -829,6 +843,7 @@ void EffectTree::UpdateEffects(int id) { UpdateEffectChanged(node, parent_node); UpdateBackfaceVisibility(node, parent_node); UpdateHasMaskingChild(node, parent_node); + UpdateOnlyDrawsVisibleContent(node, parent_node); UpdateSurfaceContentsScale(node); } @@ -1273,6 +1288,8 @@ void ScrollTree::OnScrollOffsetAnimated(ElementId id, if (!property_trees()->is_active) return; + TRACE_EVENT2("cc", "ScrollTree::OnScrollOffsetAnimated", "x", + scroll_offset.x(), "y", scroll_offset.y()); ScrollNode* scroll_node = Node(scroll_tree_index); if (SetScrollOffset(id, ClampScrollOffsetToLimits(scroll_offset, *scroll_node))) @@ -1570,6 +1587,9 @@ void ScrollTree::SetBaseScrollOffset(ElementId id, bool ScrollTree::SetScrollOffset(ElementId id, const gfx::ScrollOffset& scroll_offset) { + // TODO(crbug.com/1087088): Remove TRACE_EVENT call when the bug is fixed + TRACE_EVENT2("cc", "ScrollTree::SetScrollOffset", "x", scroll_offset.x(), "y", + scroll_offset.y()); if (property_trees()->is_main_thread) { if (scroll_offset_map_[id] == scroll_offset) return false; diff --git a/chromium/cc/trees/property_tree.h b/chromium/cc/trees/property_tree.h index 4dcf7d7a0bd..6c41097cc7a 100644 --- a/chromium/cc/trees/property_tree.h +++ b/chromium/cc/trees/property_tree.h @@ -356,6 +356,7 @@ class CC_EXPORT EffectTree final : public PropertyTree<EffectNode> { void UpdateIsDrawn(EffectNode* node, EffectNode* parent_node); void UpdateBackfaceVisibility(EffectNode* node, EffectNode* parent_node); void UpdateHasMaskingChild(EffectNode* node, EffectNode* parent_node); + void UpdateOnlyDrawsVisibleContent(EffectNode* node, EffectNode* parent_node); // Stores copy requests, keyed by node id. std::unordered_multimap<int, std::unique_ptr<viz::CopyOutputRequest>> diff --git a/chromium/cc/trees/property_tree_builder.cc b/chromium/cc/trees/property_tree_builder.cc index b036be21cbd..fcf85d88d2d 100644 --- a/chromium/cc/trees/property_tree_builder.cc +++ b/chromium/cc/trees/property_tree_builder.cc @@ -612,6 +612,7 @@ void PropertyTreeBuilderContext::AddScrollNodeIfNeeded( node.user_scrollable_vertical = layer->GetUserScrollableVertical(); node.element_id = layer->element_id(); node.transform_id = data_for_children->transform_tree_parent; + node.is_composited = true; node_id = scroll_tree_.Insert(node, parent_id); data_for_children->scroll_tree_parent = node_id; diff --git a/chromium/cc/trees/property_tree_builder_unittest.cc b/chromium/cc/trees/property_tree_builder_unittest.cc index af760bc828a..183c3342de0 100644 --- a/chromium/cc/trees/property_tree_builder_unittest.cc +++ b/chromium/cc/trees/property_tree_builder_unittest.cc @@ -201,7 +201,7 @@ TEST_F(PropertyTreeBuilderTest, RenderSurfaceListForTransparentChild) { EXPECT_EQ(1U, GetRenderSurfaceList().size()); EXPECT_EQ(static_cast<viz::RenderPassId>(root->id()), GetRenderSurfaceList().at(0)->id()); - EXPECT_EQ(gfx::Rect(), ImplOf(root)->drawable_content_rect()); + EXPECT_EQ(gfx::Rect(), ImplOf(root)->visible_drawable_content_rect()); } TEST_F(PropertyTreeBuilderTest, diff --git a/chromium/cc/trees/proxy.h b/chromium/cc/trees/proxy.h index 0fe7a90fe3f..9afdb987bfc 100644 --- a/chromium/cc/trees/proxy.h +++ b/chromium/cc/trees/proxy.h @@ -8,7 +8,6 @@ #include <memory> #include <string> -#include "base/logging.h" #include "base/memory/ref_counted.h" #include "base/threading/platform_thread.h" #include "base/time/time.h" diff --git a/chromium/cc/trees/proxy_impl.cc b/chromium/cc/trees/proxy_impl.cc index 544ad6392e3..24e222ffbe0 100644 --- a/chromium/cc/trees/proxy_impl.cc +++ b/chromium/cc/trees/proxy_impl.cc @@ -370,26 +370,10 @@ void ProxyImpl::SetVideoNeedsBeginFrames(bool needs_begin_frames) { scheduler_->SetVideoNeedsBeginFrames(needs_begin_frames); } -size_t ProxyImpl::CompositedAnimationsCount() const { - return host_impl_->mutator_host()->CompositedAnimationsCount(); -} - -size_t ProxyImpl::MainThreadAnimationsCount() const { - return host_impl_->mutator_host()->MainThreadAnimationsCount(); -} - bool ProxyImpl::HasCustomPropertyAnimations() const { return host_impl_->mutator_host()->HasCustomPropertyAnimations(); } -bool ProxyImpl::CurrentFrameHadRAF() const { - return host_impl_->mutator_host()->CurrentFrameHadRAF(); -} - -bool ProxyImpl::NextFrameHasPendingRAF() const { - return host_impl_->mutator_host()->NextFrameHasPendingRAF(); -} - bool ProxyImpl::IsInsideDraw() { return inside_draw_; } @@ -542,14 +526,37 @@ void ProxyImpl::NotifyThroughputTrackerResults(CustomTrackerResults results) { proxy_main_weak_ptr_, std::move(results))); } +void ProxyImpl::SubmitThroughputData(ukm::SourceId source_id, + int aggregated_percent, + int impl_percent, + base::Optional<int> main_percent) { + DCHECK(IsImplThread()); + MainThreadTaskRunner()->PostTask( + FROM_HERE, + base::BindOnce(&ProxyMain::SubmitThroughputData, proxy_main_weak_ptr_, + source_id, aggregated_percent, impl_percent, + main_percent)); +} + +void ProxyImpl::DidObserveFirstScrollDelay( + base::TimeDelta first_scroll_delay, + base::TimeTicks first_scroll_timestamp) { + DCHECK(IsImplThread()); + MainThreadTaskRunner()->PostTask( + FROM_HERE, base::BindOnce(&ProxyMain::DidObserveFirstScrollDelay, + proxy_main_weak_ptr_, first_scroll_delay, + first_scroll_timestamp)); +} + bool ProxyImpl::WillBeginImplFrame(const viz::BeginFrameArgs& args) { DCHECK(IsImplThread()); return host_impl_->WillBeginImplFrame(args); } -void ProxyImpl::DidFinishImplFrame() { +void ProxyImpl::DidFinishImplFrame( + const viz::BeginFrameArgs& last_activated_args) { DCHECK(IsImplThread()); - host_impl_->DidFinishImplFrame(scheduler_->last_activate_origin_frame_args()); + host_impl_->DidFinishImplFrame(last_activated_args); } void ProxyImpl::DidNotProduceFrame(const viz::BeginFrameAck& ack, @@ -703,9 +710,6 @@ DrawResult ProxyImpl::DrawInternal(bool forced_draw) { base::AutoReset<bool> mark_inside(&inside_draw_, true); - if (host_impl_->pending_tree()) - host_impl_->pending_tree()->UpdateDrawProperties(); - // This method is called on a forced draw, regardless of whether we are able // to produce a frame, as the calling site on main thread is blocked until its // request completes, and we signal completion here. If CanDraw() is false, we @@ -756,6 +760,12 @@ DrawResult ProxyImpl::DrawInternal(bool forced_draw) { proxy_main_weak_ptr_)); } + // The tile visibility/priority of the pending tree needs to be updated so + // that it doesn't get activated before the raster is complete. But this needs + // to happen after the draw, off of the critical path to draw. + if (host_impl_->pending_tree()) + host_impl_->pending_tree()->UpdateDrawProperties(); + DCHECK_NE(INVALID_RESULT, result); return result; } diff --git a/chromium/cc/trees/proxy_impl.h b/chromium/cc/trees/proxy_impl.h index 3050199e43d..dbe9b20d3ba 100644 --- a/chromium/cc/trees/proxy_impl.h +++ b/chromium/cc/trees/proxy_impl.h @@ -119,10 +119,18 @@ class CC_EXPORT ProxyImpl : public LayerTreeHostImplClient, void NotifyPaintWorkletStateChange( Scheduler::PaintWorkletState state) override; void NotifyThroughputTrackerResults(CustomTrackerResults results) override; + void SubmitThroughputData(ukm::SourceId source_id, + int aggregated_percent, + int impl_percent, + base::Optional<int> main_percent) override; + void DidObserveFirstScrollDelay( + base::TimeDelta first_scroll_delay, + base::TimeTicks first_scroll_timestamp) override; // SchedulerClient implementation bool WillBeginImplFrame(const viz::BeginFrameArgs& args) override; - void DidFinishImplFrame() override; + void DidFinishImplFrame( + const viz::BeginFrameArgs& last_activated_args) override; void DidNotProduceFrame(const viz::BeginFrameAck& ack, FrameSkippedReason reason) override; void WillNotReceiveBeginFrame() override; @@ -140,11 +148,7 @@ class CC_EXPORT ProxyImpl : public LayerTreeHostImplClient, void ScheduledActionBeginMainFrameNotExpectedUntil( base::TimeTicks time) override; void FrameIntervalUpdated(base::TimeDelta interval) override {} - size_t CompositedAnimationsCount() const override; - size_t MainThreadAnimationsCount() const override; bool HasCustomPropertyAnimations() const override; - bool CurrentFrameHadRAF() const override; - bool NextFrameHasPendingRAF() const override; DrawResult DrawInternal(bool forced_draw); diff --git a/chromium/cc/trees/proxy_main.cc b/chromium/cc/trees/proxy_main.cc index 22976f62b02..1ad9795cd11 100644 --- a/chromium/cc/trees/proxy_main.cc +++ b/chromium/cc/trees/proxy_main.cc @@ -378,6 +378,22 @@ void ProxyMain::NotifyThroughputTrackerResults(CustomTrackerResults results) { layer_tree_host_->NotifyThroughputTrackerResults(std::move(results)); } +void ProxyMain::SubmitThroughputData(ukm::SourceId source_id, + int aggregated_percent, + int impl_percent, + base::Optional<int> main_percent) { + DCHECK(!task_runner_provider_->IsImplThread()); + layer_tree_host_->SubmitThroughputData(source_id, aggregated_percent, + impl_percent, main_percent); +} + +void ProxyMain::DidObserveFirstScrollDelay( + base::TimeDelta first_scroll_delay, + base::TimeTicks first_scroll_timestamp) { + layer_tree_host_->DidObserveFirstScrollDelay(first_scroll_delay, + first_scroll_timestamp); +} + bool ProxyMain::IsStarted() const { DCHECK(IsMainThread()); return started_; diff --git a/chromium/cc/trees/proxy_main.h b/chromium/cc/trees/proxy_main.h index 8738cb1f38f..050af82306b 100644 --- a/chromium/cc/trees/proxy_main.h +++ b/chromium/cc/trees/proxy_main.h @@ -58,6 +58,12 @@ class CC_EXPORT ProxyMain : public Proxy { std::vector<LayerTreeHost::PresentationTimeCallback> callbacks, const gfx::PresentationFeedback& feedback); void NotifyThroughputTrackerResults(CustomTrackerResults results); + void SubmitThroughputData(ukm::SourceId source_id, + int aggregated_percent, + int impl_percent, + base::Optional<int> main_percent); + void DidObserveFirstScrollDelay(base::TimeDelta first_scroll_delay, + base::TimeTicks first_scroll_timestamp); CommitPipelineStage max_requested_pipeline_stage() const { return max_requested_pipeline_stage_; diff --git a/chromium/cc/trees/render_frame_metadata.h b/chromium/cc/trees/render_frame_metadata.h index 43af9536c47..2b884785a9d 100644 --- a/chromium/cc/trees/render_frame_metadata.h +++ b/chromium/cc/trees/render_frame_metadata.h @@ -41,8 +41,7 @@ class CC_EXPORT RenderFrameMetadata { // specified. SkColor root_background_color = SK_ColorWHITE; - // Scroll offset of the root layer. This optional parameter is only valid - // during tests. + // Scroll offset of the root layer. base::Optional<gfx::Vector2dF> root_scroll_offset; // Selection region relative to the current viewport. If the selection is @@ -56,6 +55,11 @@ class CC_EXPORT RenderFrameMetadata { // are the same). bool is_mobile_optimized = false; + // Flag used to notify the browser process to start or stop forwarding points + // to viz for use in a delegated ink trail. True the entire time points should + // be forwarded, and forwarding stops as soon as it is false again. + bool has_delegated_ink_metadata = false; + // The device scale factor used to generate a CompositorFrame. float device_scale_factor = 1.f; diff --git a/chromium/cc/trees/scroll_and_scale_set.h b/chromium/cc/trees/scroll_and_scale_set.h index cef0850f33b..b7e9d9ed91a 100644 --- a/chromium/cc/trees/scroll_and_scale_set.h +++ b/chromium/cc/trees/scroll_and_scale_set.h @@ -97,6 +97,10 @@ struct CC_EXPORT ScrollAndScaleSet { // ended. bool scroll_gesture_did_end; + // Tracks whether there is an ongoing compositor-driven animation for a + // scroll. + bool ongoing_scroll_animation = false; + // Tracks different methods of scrolling (e.g. wheel, touch, precision // touchpad, etc.). ManipulationInfo manipulation_info; diff --git a/chromium/cc/trees/single_thread_proxy.cc b/chromium/cc/trees/single_thread_proxy.cc index 6f469a42c4e..c5449d5e85a 100644 --- a/chromium/cc/trees/single_thread_proxy.cc +++ b/chromium/cc/trees/single_thread_proxy.cc @@ -415,26 +415,10 @@ void SingleThreadProxy::SetVideoNeedsBeginFrames(bool needs_begin_frames) { scheduler_on_impl_thread_->SetVideoNeedsBeginFrames(needs_begin_frames); } -size_t SingleThreadProxy::CompositedAnimationsCount() const { - return 0; -} - -size_t SingleThreadProxy::MainThreadAnimationsCount() const { - return 0; -} - bool SingleThreadProxy::HasCustomPropertyAnimations() const { return false; } -bool SingleThreadProxy::CurrentFrameHadRAF() const { - return false; -} - -bool SingleThreadProxy::NextFrameHasPendingRAF() const { - return false; -} - bool SingleThreadProxy::IsInsideDraw() { return inside_draw_; } @@ -635,7 +619,7 @@ void SingleThreadProxy::CompositeImmediately(base::TimeTicks frame_begin_time, // another draw will never be scheduled, so break remaining promises. host_impl_->active_tree()->BreakSwapPromises(SwapPromise::SWAP_FAILS); - DidFinishImplFrame(); + DidFinishImplFrame(begin_frame_args); } } @@ -958,9 +942,9 @@ void SingleThreadProxy::ScheduledActionPerformImplSideInvalidation() { NotifyReadyToActivate(); } -void SingleThreadProxy::DidFinishImplFrame() { - host_impl_->DidFinishImplFrame( - scheduler_on_impl_thread_->last_activate_origin_frame_args()); +void SingleThreadProxy::DidFinishImplFrame( + const viz::BeginFrameArgs& last_activated_args) { + host_impl_->DidFinishImplFrame(last_activated_args); #if DCHECK_IS_ON() DCHECK(inside_impl_frame_) << "DidFinishImplFrame called while not inside an impl frame!"; diff --git a/chromium/cc/trees/single_thread_proxy.h b/chromium/cc/trees/single_thread_proxy.h index 96228af593c..ee1915c5222 100644 --- a/chromium/cc/trees/single_thread_proxy.h +++ b/chromium/cc/trees/single_thread_proxy.h @@ -77,7 +77,8 @@ class CC_EXPORT SingleThreadProxy : public Proxy, // SchedulerClient implementation bool WillBeginImplFrame(const viz::BeginFrameArgs& args) override; - void DidFinishImplFrame() override; + void DidFinishImplFrame( + const viz::BeginFrameArgs& last_activated_args) override; void DidNotProduceFrame(const viz::BeginFrameAck& ack, FrameSkippedReason reason) override; void WillNotReceiveBeginFrame() override; @@ -95,11 +96,7 @@ class CC_EXPORT SingleThreadProxy : public Proxy, void ScheduledActionBeginMainFrameNotExpectedUntil( base::TimeTicks time) override; void FrameIntervalUpdated(base::TimeDelta interval) override; - size_t CompositedAnimationsCount() const override; - size_t MainThreadAnimationsCount() const override; bool HasCustomPropertyAnimations() const override; - bool CurrentFrameHadRAF() const override; - bool NextFrameHasPendingRAF() const override; // LayerTreeHostImplClient implementation void DidLoseLayerTreeFrameSinkOnImplThread() override; @@ -137,9 +134,21 @@ class CC_EXPORT SingleThreadProxy : public Proxy, void NotifyPaintWorkletStateChange( Scheduler::PaintWorkletState state) override; void NotifyThroughputTrackerResults(CustomTrackerResults results) override; + void SubmitThroughputData(ukm::SourceId source_id, + int aggregated_percent, + int impl_percent, + base::Optional<int> main_percent) override {} void RequestNewLayerTreeFrameSink(); + void DidObserveFirstScrollDelay( + base::TimeDelta first_scroll_delay, + base::TimeTicks first_scroll_timestamp) override { + // Single-threaded mode is only for browser compositing and for renderers in + // layout tests. This will still get called in the latter case, but we don't + // need to record UKM in that case. + } + // Called by the legacy path where RenderWidget does the scheduling. // Rasterization of tiles is only performed when |raster| is true. void CompositeImmediately(base::TimeTicks frame_begin_time, bool raster); diff --git a/chromium/cc/trees/task_runner_provider.h b/chromium/cc/trees/task_runner_provider.h index 0bc19d31f38..232bc6da0f6 100644 --- a/chromium/cc/trees/task_runner_provider.h +++ b/chromium/cc/trees/task_runner_provider.h @@ -8,7 +8,7 @@ #include <memory> #include <string> -#include "base/logging.h" +#include "base/check.h" #include "base/memory/ptr_util.h" #include "base/memory/ref_counted.h" #include "base/single_thread_task_runner.h" diff --git a/chromium/cc/trees/transform_node.cc b/chromium/cc/trees/transform_node.cc index 780bba5ec67..b19c820baab 100644 --- a/chromium/cc/trees/transform_node.cc +++ b/chromium/cc/trees/transform_node.cc @@ -32,6 +32,7 @@ TransformNode::TransformNode() moved_by_outer_viewport_bounds_delta_y(false), in_subtree_of_page_scale_layer(false), transform_changed(false), + delegates_to_parent_for_backface(false), maximum_animation_scale(kNotScaled), starting_animation_scale(kNotScaled) {} @@ -62,6 +63,8 @@ bool TransformNode::operator==(const TransformNode& other) const { other.moved_by_outer_viewport_bounds_delta_y && in_subtree_of_page_scale_layer == other.in_subtree_of_page_scale_layer && + delegates_to_parent_for_backface == + other.delegates_to_parent_for_backface && transform_changed == other.transform_changed && scroll_offset == other.scroll_offset && snap_amount == other.snap_amount && diff --git a/chromium/cc/trees/transform_node.h b/chromium/cc/trees/transform_node.h index 401af01e617..47bb059ea1c 100644 --- a/chromium/cc/trees/transform_node.h +++ b/chromium/cc/trees/transform_node.h @@ -105,6 +105,10 @@ struct CC_EXPORT TransformNode { // We need to track changes to to_screen transform to compute the damage rect. bool transform_changed : 1; + // Whether the parent transform node should be used for checking backface + // visibility, not this transform one. + bool delegates_to_parent_for_backface : 1; + gfx::ScrollOffset scroll_offset; // This value stores the snapped amount whenever we snap. If the snap is due diff --git a/chromium/cc/trees/ukm_manager.cc b/chromium/cc/trees/ukm_manager.cc index 2141cf1a21c..9d4eab1315f 100644 --- a/chromium/cc/trees/ukm_manager.cc +++ b/chromium/cc/trees/ukm_manager.cc @@ -4,7 +4,12 @@ #include "cc/trees/ukm_manager.h" +#include <algorithm> +#include <utility> + +#include "cc/metrics/compositor_frame_reporter.h" #include "cc/metrics/throughput_ukm_reporter.h" +#include "components/viz/common/quads/compositor_frame.h" #include "services/metrics/public/cpp/ukm_builders.h" #include "services/metrics/public/cpp/ukm_recorder.h" @@ -187,11 +192,13 @@ void UkmManager::RecordAggregateThroughput(AggregationType aggregation_type, builder.Record(recorder_.get()); } -void UkmManager::RecordLatencyUKM( +void UkmManager::RecordCompositorLatencyUKM( CompositorFrameReporter::FrameReportType report_type, const std::vector<CompositorFrameReporter::StageData>& stage_history, const CompositorFrameReporter::ActiveTrackers& active_trackers, const viz::FrameTimingDetails& viz_breakdown) const { + using StageType = CompositorFrameReporter::StageType; + ukm::builders::Graphics_Smoothness_Latency builder(source_id_); if (report_type == CompositorFrameReporter::FrameReportType::kDroppedFrame) { @@ -202,7 +209,7 @@ void UkmManager::RecordLatencyUKM( for (const CompositorFrameReporter::StageData& stage : stage_history) { switch (stage.stage_type) { #define CASE_FOR_STAGE(name) \ - case CompositorFrameReporter::StageType::k##name: \ + case StageType::k##name: \ builder.Set##name((stage.end_time - stage.start_time).InMicroseconds()); \ break; CASE_FOR_STAGE(BeginImplFrameToSendBeginMainFrame); @@ -215,8 +222,7 @@ void UkmManager::RecordLatencyUKM( #undef CASE_FOR_STAGE // Break out kSubmitCompositorFrameToPresentationCompositorFrame to report // the viz breakdown. - case CompositorFrameReporter::StageType:: - kSubmitCompositorFrameToPresentationCompositorFrame: + case StageType::kSubmitCompositorFrameToPresentationCompositorFrame: builder.SetSubmitCompositorFrameToPresentationCompositorFrame( (stage.end_time - stage.start_time).InMicroseconds()); if (viz_breakdown.received_compositor_frame_timestamp.is_null()) @@ -287,4 +293,80 @@ void UkmManager::RecordLatencyUKM( builder.Record(recorder_.get()); } +void UkmManager::RecordEventLatencyUKM( + const std::vector<EventMetrics>& events_metrics, + const std::vector<CompositorFrameReporter::StageData>& stage_history, + const viz::FrameTimingDetails& viz_breakdown) const { + using StageType = CompositorFrameReporter::StageType; + + for (const EventMetrics& event_metrics : events_metrics) { + ukm::builders::Graphics_Smoothness_EventLatency builder(source_id_); + + builder.SetEventType(static_cast<int64_t>(event_metrics.type())); + + if (event_metrics.scroll_type()) { + builder.SetScrollInputType( + static_cast<int64_t>(*event_metrics.scroll_type())); + + if (!viz_breakdown.swap_timings.is_null()) { + builder.SetTotalLatencyToSwapEnd( + (viz_breakdown.swap_timings.swap_end - event_metrics.time_stamp()) + .InMicroseconds()); + } + } + + // It is possible for an event to arrive in the compositor in the middle of + // a frame (e.g. the browser received the event *after* renderer received a + // begin-impl, and the event reached the compositor before that frame + // ended). To handle such cases, find the first stage that happens after the + // event's arrival in the browser. + auto stage_it = std::find_if( + stage_history.begin(), stage_history.end(), + [&event_metrics](const CompositorFrameReporter::StageData& stage) { + return stage.start_time > event_metrics.time_stamp(); + }); + // TODO(crbug.com/1079116): Ideally, at least the start time of + // SubmitCompositorFrameToPresentationCompositorFrame stage should be + // greater than the event time stamp, but apparently, this is not always the + // case (see crbug.com/1093698). For now, skip to the next event in such + // cases. Hopefully, the work to reduce discrepancies between the new + // EventLatency and the old Event.Latency metrics would fix this issue. If + // not, we need to reconsider investigating this issue. + if (stage_it == stage_history.end()) + continue; + + builder.SetBrowserToRendererCompositor( + (stage_it->start_time - event_metrics.time_stamp()).InMicroseconds()); + + for (; stage_it != stage_history.end(); ++stage_it) { + // Total latency is calculated since the event timestamp. + const base::TimeTicks start_time = + stage_it->stage_type == StageType::kTotalLatency + ? event_metrics.time_stamp() + : stage_it->start_time; + + switch (stage_it->stage_type) { +#define CASE_FOR_STAGE(name) \ + case StageType::k##name: \ + builder.Set##name((stage_it->end_time - start_time).InMicroseconds()); \ + break; + CASE_FOR_STAGE(BeginImplFrameToSendBeginMainFrame); + CASE_FOR_STAGE(SendBeginMainFrameToCommit); + CASE_FOR_STAGE(Commit); + CASE_FOR_STAGE(EndCommitToActivation); + CASE_FOR_STAGE(Activation); + CASE_FOR_STAGE(EndActivateToSubmitCompositorFrame); + CASE_FOR_STAGE(SubmitCompositorFrameToPresentationCompositorFrame); + CASE_FOR_STAGE(TotalLatency); +#undef CASE_FOR_STAGE + default: + NOTREACHED(); + break; + } + } + + builder.Record(recorder_.get()); + } +} + } // namespace cc diff --git a/chromium/cc/trees/ukm_manager.h b/chromium/cc/trees/ukm_manager.h index 50653814f85..6a2cc1c03db 100644 --- a/chromium/cc/trees/ukm_manager.h +++ b/chromium/cc/trees/ukm_manager.h @@ -5,9 +5,14 @@ #ifndef CC_TREES_UKM_MANAGER_H_ #define CC_TREES_UKM_MANAGER_H_ +#include <memory> +#include <vector> + #include "cc/cc_export.h" #include "cc/metrics/compositor_frame_reporter.h" +#include "cc/metrics/event_metrics.h" #include "cc/metrics/frame_sequence_metrics.h" +#include "components/viz/common/frame_timing_details.h" #include "services/metrics/public/cpp/ukm_source_id.h" #include "url/gurl.h" @@ -33,6 +38,7 @@ class CC_EXPORT UkmManager { ~UkmManager(); void SetSourceId(ukm::SourceId source_id); + ukm::SourceId source_id() const { return source_id_; } // These metrics are recorded while a user interaction is in progress. void SetUserInteractionInProgress(bool in_progress); @@ -48,12 +54,17 @@ class CC_EXPORT UkmManager { int64_t throughput) const; void RecordAggregateThroughput(AggregationType aggregation_type, int64_t throughput_percent) const; - void RecordLatencyUKM( + void RecordCompositorLatencyUKM( CompositorFrameReporter::FrameReportType report_type, const std::vector<CompositorFrameReporter::StageData>& stage_history, const CompositorFrameReporter::ActiveTrackers& active_trackers, const viz::FrameTimingDetails& viz_breakdown) const; + void RecordEventLatencyUKM( + const std::vector<EventMetrics>& events_metrics, + const std::vector<CompositorFrameReporter::StageData>& stage_history, + const viz::FrameTimingDetails& viz_breakdown) const; + ukm::UkmRecorder* recorder_for_testing() { return recorder_.get(); } private: diff --git a/chromium/cc/trees/ukm_manager_unittest.cc b/chromium/cc/trees/ukm_manager_unittest.cc index 9f4dd926f5b..2b55162d45f 100644 --- a/chromium/cc/trees/ukm_manager_unittest.cc +++ b/chromium/cc/trees/ukm_manager_unittest.cc @@ -4,7 +4,14 @@ #include "cc/trees/ukm_manager.h" +#include <vector> + +#include "base/time/time.h" +#include "cc/metrics/compositor_frame_reporter.h" +#include "cc/metrics/event_metrics.h" #include "components/ukm/test_ukm_recorder.h" +#include "components/viz/common/frame_timing_details.h" +#include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" @@ -23,6 +30,55 @@ const char kCheckerboardAreaRatio[] = "CheckerboardedContentAreaRatio"; const char kMissingTiles[] = "NumMissingTiles"; const char kCheckerboardedImagesCount[] = "CheckerboardedImagesCount"; +// Names of compositor/event latency UKM events. +const char kCompositorLatency[] = "Graphics.Smoothness.Latency"; +const char kEventLatency[] = "Graphics.Smoothness.EventLatency"; + +// Names of enum metrics used in compositor/event latency UKM metrics. +const char kMissedFrame[] = "MissedFrame"; +const char kEventType[] = "EventType"; +const char kScrollInputType[] = "ScrollInputType"; + +// Names of compositor stages and substages used in compositor/event latency UKM +// metrics. +const char kBrowserToRendererCompositor[] = "BrowserToRendererCompositor"; +const char kBeginImplFrameToSendBeginMainFrame[] = + "BeginImplFrameToSendBeginMainFrame"; +const char kSendBeginMainFrameToCommit[] = "SendBeginMainFrameToCommit"; +const char kCommit[] = "Commit"; +const char kEndCommitToActivation[] = "EndCommitToActivation"; +const char kActivation[] = "Activation"; +const char kEndActivateToSubmitCompositorFrame[] = + "EndActivateToSubmitCompositorFrame"; +const char kSubmitCompositorFrameToPresentationCompositorFrame[] = + "SubmitCompositorFrameToPresentationCompositorFrame"; +const char kVizBreakdownSubmitToReceiveCompositorFrame[] = + "SubmitCompositorFrameToPresentationCompositorFrame." + "SubmitToReceiveCompositorFrame"; +const char kVizBreakdownReceivedCompositorFrameToStartDraw[] = + "SubmitCompositorFrameToPresentationCompositorFrame." + "ReceivedCompositorFrameToStartDraw"; +const char kVizBreakdownStartDrawToSwapStart[] = + "SubmitCompositorFrameToPresentationCompositorFrame.StartDrawToSwapStart"; +const char kVizBreakdownSwapStartToSwapEnd[] = + "SubmitCompositorFrameToPresentationCompositorFrame.SwapStartToSwapEnd"; +const char kVizBreakdownSwapEndToPresentationCompositorFrame[] = + "SubmitCompositorFrameToPresentationCompositorFrame." + "SwapEndToPresentationCompositorFrame"; +const char kTotalLatencyToSwapEnd[] = "TotalLatencyToSwapEnd"; +const char kTotalLatency[] = "TotalLatency"; + +// Names of frame sequence types use in compositor latency UKM metrics (see +// FrameSequenceTrackerType enum). +const char kCompositorAnimation[] = "CompositorAnimation"; +const char kMainThreadAnimation[] = "MainThreadAnimation"; +const char kPinchZoom[] = "PinchZoom"; +const char kRAF[] = "RAF"; +const char kTouchScroll[] = "TouchScroll"; +const char kUniversal[] = "Universal"; +const char kVideo[] = "Video"; +const char kWheelScroll[] = "WheelScroll"; + class UkmManagerTest : public testing::Test { public: UkmManagerTest() { @@ -36,6 +92,8 @@ class UkmManagerTest : public testing::Test { manager_->SetSourceId(kTestSourceId1); } + ~UkmManagerTest() override = default; + protected: ukm::TestUkmRecorder* test_ukm_recorder_; std::unique_ptr<UkmManager> manager_; @@ -99,5 +157,300 @@ TEST_F(UkmManagerTest, Basic) { } } +class UkmManagerCompositorLatencyTest + : public UkmManagerTest, + public testing::WithParamInterface< + CompositorFrameReporter::FrameReportType> { + public: + UkmManagerCompositorLatencyTest() : report_type_(GetParam()) {} + ~UkmManagerCompositorLatencyTest() override = default; + + protected: + CompositorFrameReporter::FrameReportType report_type() const { + return report_type_; + } + + private: + CompositorFrameReporter::FrameReportType report_type_; +}; + +INSTANTIATE_TEST_SUITE_P( + All, + UkmManagerCompositorLatencyTest, + testing::Values( + CompositorFrameReporter::FrameReportType::kNonDroppedFrame, + CompositorFrameReporter::FrameReportType::kMissedDeadlineFrame, + CompositorFrameReporter::FrameReportType::kDroppedFrame, + CompositorFrameReporter::FrameReportType::kCompositorOnlyFrame)); + +TEST_P(UkmManagerCompositorLatencyTest, CompositorLatency) { + base::TimeTicks now = base::TimeTicks::Now(); + + const base::TimeTicks begin_impl_time = + (now += base::TimeDelta::FromMicroseconds(10)); + const base::TimeTicks begin_main_time = + (now += base::TimeDelta::FromMicroseconds(10)); + const base::TimeTicks begin_commit_time = + (now += base::TimeDelta::FromMicroseconds(10)); + const base::TimeTicks end_commit_time = + (now += base::TimeDelta::FromMicroseconds(10)); + const base::TimeTicks begin_activate_time = + (now += base::TimeDelta::FromMicroseconds(10)); + const base::TimeTicks end_activate_time = + (now += base::TimeDelta::FromMicroseconds(10)); + const base::TimeTicks submit_time = + (now += base::TimeDelta::FromMicroseconds(10)); + + viz::FrameTimingDetails viz_breakdown; + viz_breakdown.received_compositor_frame_timestamp = + (now += base::TimeDelta::FromMicroseconds(1)); + viz_breakdown.draw_start_timestamp = + (now += base::TimeDelta::FromMicroseconds(2)); + viz_breakdown.swap_timings.swap_start = + (now += base::TimeDelta::FromMicroseconds(3)); + viz_breakdown.swap_timings.swap_end = + (now += base::TimeDelta::FromMicroseconds(4)); + viz_breakdown.presentation_feedback.timestamp = + (now += base::TimeDelta::FromMicroseconds(5)); + + std::vector<CompositorFrameReporter::StageData> stage_history = { + { + CompositorFrameReporter::StageType:: + kBeginImplFrameToSendBeginMainFrame, + begin_impl_time, + begin_main_time, + }, + { + CompositorFrameReporter::StageType::kSendBeginMainFrameToCommit, + begin_main_time, + begin_commit_time, + }, + { + CompositorFrameReporter::StageType::kCommit, + begin_commit_time, + end_commit_time, + }, + { + CompositorFrameReporter::StageType::kEndCommitToActivation, + end_commit_time, + begin_activate_time, + }, + { + CompositorFrameReporter::StageType::kActivation, + begin_activate_time, + end_activate_time, + }, + { + CompositorFrameReporter::StageType:: + kEndActivateToSubmitCompositorFrame, + end_activate_time, + submit_time, + }, + { + CompositorFrameReporter::StageType:: + kSubmitCompositorFrameToPresentationCompositorFrame, + submit_time, + viz_breakdown.presentation_feedback.timestamp, + }, + { + CompositorFrameReporter::StageType::kTotalLatency, + begin_impl_time, + viz_breakdown.presentation_feedback.timestamp, + }, + }; + + CompositorFrameReporter::ActiveTrackers active_trackers; + active_trackers.set( + static_cast<size_t>(FrameSequenceTrackerType::kTouchScroll)); + active_trackers.set( + static_cast<size_t>(FrameSequenceTrackerType::kCompositorAnimation)); + active_trackers.set( + static_cast<size_t>(FrameSequenceTrackerType::kUniversal)); + + manager_->RecordCompositorLatencyUKM(report_type(), stage_history, + active_trackers, viz_breakdown); + + const auto& entries = + test_ukm_recorder_->GetEntriesByName(kCompositorLatency); + EXPECT_EQ(1u, entries.size()); + const auto* entry = entries[0]; + + EXPECT_NE(ukm::kInvalidSourceId, entry->source_id); + test_ukm_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestUrl)); + + if (report_type() == + CompositorFrameReporter::FrameReportType::kDroppedFrame) { + test_ukm_recorder_->ExpectEntryMetric(entry, kMissedFrame, true); + } else { + EXPECT_FALSE(test_ukm_recorder_->EntryHasMetric(entry, kMissedFrame)); + } + + test_ukm_recorder_->ExpectEntryMetric( + entry, kBeginImplFrameToSendBeginMainFrame, + (begin_main_time - begin_impl_time).InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric( + entry, kSendBeginMainFrameToCommit, + (begin_commit_time - begin_main_time).InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric( + entry, kCommit, (end_commit_time - begin_commit_time).InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric( + entry, kEndCommitToActivation, + (begin_activate_time - end_commit_time).InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric( + entry, kActivation, + (end_activate_time - begin_activate_time).InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric( + entry, kEndActivateToSubmitCompositorFrame, + (submit_time - end_activate_time).InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric( + entry, kSubmitCompositorFrameToPresentationCompositorFrame, + (viz_breakdown.presentation_feedback.timestamp - submit_time) + .InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric( + entry, kVizBreakdownSubmitToReceiveCompositorFrame, + (viz_breakdown.received_compositor_frame_timestamp - submit_time) + .InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric( + entry, kVizBreakdownReceivedCompositorFrameToStartDraw, + (viz_breakdown.draw_start_timestamp - + viz_breakdown.received_compositor_frame_timestamp) + .InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric(entry, + kVizBreakdownStartDrawToSwapStart, + (viz_breakdown.swap_timings.swap_start - + viz_breakdown.draw_start_timestamp) + .InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric(entry, kVizBreakdownSwapStartToSwapEnd, + (viz_breakdown.swap_timings.swap_end - + viz_breakdown.swap_timings.swap_start) + .InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric( + entry, kVizBreakdownSwapEndToPresentationCompositorFrame, + (viz_breakdown.presentation_feedback.timestamp - + viz_breakdown.swap_timings.swap_end) + .InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric( + entry, kTotalLatency, + (viz_breakdown.presentation_feedback.timestamp - begin_impl_time) + .InMicroseconds()); + + test_ukm_recorder_->ExpectEntryMetric(entry, kCompositorAnimation, true); + test_ukm_recorder_->ExpectEntryMetric(entry, kTouchScroll, true); + EXPECT_FALSE(test_ukm_recorder_->EntryHasMetric(entry, kMainThreadAnimation)); + EXPECT_FALSE(test_ukm_recorder_->EntryHasMetric(entry, kPinchZoom)); + EXPECT_FALSE(test_ukm_recorder_->EntryHasMetric(entry, kRAF)); + EXPECT_FALSE(test_ukm_recorder_->EntryHasMetric(entry, kUniversal)); + EXPECT_FALSE(test_ukm_recorder_->EntryHasMetric(entry, kVideo)); + EXPECT_FALSE(test_ukm_recorder_->EntryHasMetric(entry, kWheelScroll)); +} + +TEST_F(UkmManagerTest, EventLatency) { + base::TimeTicks now = base::TimeTicks::Now(); + + const base::TimeTicks event_time = now; + std::unique_ptr<EventMetrics> event_metrics_ptrs[] = { + EventMetrics::Create(ui::ET_GESTURE_SCROLL_BEGIN, event_time, + ui::ScrollInputType::kWheel), + EventMetrics::Create(ui::ET_GESTURE_SCROLL_UPDATE, event_time, + ui::ScrollInputType::kWheel), + EventMetrics::Create(ui::ET_GESTURE_SCROLL_UPDATE, event_time, + ui::ScrollInputType::kWheel), + }; + EXPECT_THAT(event_metrics_ptrs, ::testing::Each(::testing::NotNull())); + std::vector<EventMetrics> events_metrics = { + *event_metrics_ptrs[0], *event_metrics_ptrs[1], *event_metrics_ptrs[2]}; + + const base::TimeTicks begin_impl_time = + (now += base::TimeDelta::FromMicroseconds(10)); + const base::TimeTicks end_activate_time = + (now += base::TimeDelta::FromMicroseconds(10)); + const base::TimeTicks submit_time = + (now += base::TimeDelta::FromMicroseconds(10)); + + viz::FrameTimingDetails viz_breakdown; + viz_breakdown.received_compositor_frame_timestamp = + (now += base::TimeDelta::FromMicroseconds(1)); + viz_breakdown.draw_start_timestamp = + (now += base::TimeDelta::FromMicroseconds(2)); + viz_breakdown.swap_timings.swap_start = + (now += base::TimeDelta::FromMicroseconds(3)); + viz_breakdown.swap_timings.swap_end = + (now += base::TimeDelta::FromMicroseconds(4)); + viz_breakdown.presentation_feedback.timestamp = + (now += base::TimeDelta::FromMicroseconds(5)); + + const base::TimeTicks swap_end_time = viz_breakdown.swap_timings.swap_end; + const base::TimeTicks present_time = + viz_breakdown.presentation_feedback.timestamp; + + std::vector<CompositorFrameReporter::StageData> stage_history = { + { + CompositorFrameReporter::StageType:: + kBeginImplFrameToSendBeginMainFrame, + begin_impl_time, + end_activate_time, + }, + { + CompositorFrameReporter::StageType:: + kEndActivateToSubmitCompositorFrame, + end_activate_time, + submit_time, + }, + { + CompositorFrameReporter::StageType:: + kSubmitCompositorFrameToPresentationCompositorFrame, + submit_time, + present_time, + }, + { + CompositorFrameReporter::StageType::kTotalLatency, + event_time, + present_time, + }, + }; + + manager_->RecordEventLatencyUKM(events_metrics, stage_history, viz_breakdown); + + const auto& entries = test_ukm_recorder_->GetEntriesByName(kEventLatency); + EXPECT_EQ(3u, entries.size()); + for (size_t i = 0; i < entries.size(); i++) { + const auto* entry = entries[i]; + const auto* event_metrics = event_metrics_ptrs[i].get(); + + EXPECT_NE(ukm::kInvalidSourceId, entry->source_id); + test_ukm_recorder_->ExpectEntrySourceHasUrl(entry, GURL(kTestUrl)); + + test_ukm_recorder_->ExpectEntryMetric( + entry, kEventType, static_cast<int64_t>(event_metrics->type())); + test_ukm_recorder_->ExpectEntryMetric( + entry, kScrollInputType, + static_cast<int64_t>(*event_metrics->scroll_type())); + + test_ukm_recorder_->ExpectEntryMetric( + entry, kBrowserToRendererCompositor, + (begin_impl_time - event_time).InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric( + entry, kBeginImplFrameToSendBeginMainFrame, + (end_activate_time - begin_impl_time).InMicroseconds()); + EXPECT_FALSE( + test_ukm_recorder_->EntryHasMetric(entry, kSendBeginMainFrameToCommit)); + EXPECT_FALSE(test_ukm_recorder_->EntryHasMetric(entry, kCommit)); + EXPECT_FALSE( + test_ukm_recorder_->EntryHasMetric(entry, kEndCommitToActivation)); + EXPECT_FALSE(test_ukm_recorder_->EntryHasMetric(entry, kActivation)); + test_ukm_recorder_->ExpectEntryMetric( + entry, kEndActivateToSubmitCompositorFrame, + (submit_time - end_activate_time).InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric( + entry, kSubmitCompositorFrameToPresentationCompositorFrame, + (present_time - submit_time).InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric( + entry, kTotalLatencyToSwapEnd, + (swap_end_time - event_time).InMicroseconds()); + test_ukm_recorder_->ExpectEntryMetric( + entry, kTotalLatency, (present_time - event_time).InMicroseconds()); + } +} + } // namespace } // namespace cc |