0

cc: Plumb info for computing low velocity fling

Info made available to LayerTreeHostImpl to compute info for detecthing
(and thus throttling) low velocity fling:
* has_raw_input. This is distinct from the existing has_input that it is
  not set during fling.
* Report and remember scroll offset
* Remember the last begin frame arg to compute the time difference
  between frames.

Compute fling speed in pixels per second. Add a for the computed
speed.

Bug: 402442892
Change-Id: Id437aa33d597bc2e532758722fcb8e6fa3bd0467
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6408736
Reviewed-by: Jonathan Ross <jonross@chromium.org>
Commit-Queue: Bo Liu <boliu@chromium.org>
Reviewed-by: David Bokan <bokan@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1442338}
This commit is contained in:
Bo Liu
2025-04-03 12:04:23 -07:00
committed by Chromium LUCI CQ
parent c587e9920c
commit faa20082a1
13 changed files with 142 additions and 37 deletions

@ -154,7 +154,7 @@ class CompositorDelegateForInput {
virtual double PredictViewportBoundsDelta(
double current_bounds_delta,
gfx::Vector2dF scroll_distance) const = 0;
virtual void NotifyInputEvent() = 0;
virtual void NotifyInputEvent(bool is_fling) = 0;
virtual bool ElementHasImplOnlyScrollAnimation(
ElementId element_id) const = 0;
virtual std::optional<gfx::PointF> UpdateImplAnimationScrollTargetWithDelta(
@ -167,7 +167,6 @@ class CompositorDelegateForInput {
virtual void SetNeedsFullViewportRedraw() = 0;
virtual void SetDeferBeginMainFrame(bool defer_begin_main_frame) const = 0;
virtual void DidUpdateScrollAnimationCurve() = 0;
virtual void AccumulateScrollDeltaForTracing(const gfx::Vector2dF& delta) = 0;
virtual void DidStartPinchZoom() = 0;
virtual void DidUpdatePinchZoom() = 0;
virtual void DidEndPinchZoom() = 0;
@ -176,7 +175,9 @@ class CompositorDelegateForInput {
virtual void DidMouseLeave() = 0;
virtual bool IsInHighLatencyMode() const = 0;
virtual void WillScrollContent(ElementId element_id) = 0;
virtual void DidScrollContent(ElementId element_id, bool animated) = 0;
virtual void DidScrollContent(ElementId element_id,
bool animated,
const gfx::Vector2dF& scroll_delta) = 0;
virtual float DeviceScaleFactor() const = 0;
virtual float PageScaleFactor() const = 0;
virtual gfx::Size VisualDeviceViewportSize() const = 0;

@ -353,9 +353,6 @@ InputHandlerScrollResult InputHandler::ScrollUpdate(
ui::ScrollGranularity::kScrollByPixel;
}
compositor_delegate_->AccumulateScrollDeltaForTracing(
gfx::Vector2dF(scroll_state.delta_x(), scroll_state.delta_y()));
compositor_delegate_->WillScrollContent(scroll_node.element_id);
float initial_top_controls_offset =
@ -371,8 +368,8 @@ InputHandlerScrollResult InputHandler::ScrollUpdate(
bool did_scroll_content = did_scroll_x || did_scroll_y;
if (did_scroll_content) {
bool is_animated_scroll = ShouldAnimateScroll(scroll_state);
compositor_delegate_->DidScrollContent(scroll_node.element_id,
is_animated_scroll);
compositor_delegate_->DidScrollContent(
scroll_node.element_id, is_animated_scroll, resolvedScrollDelta);
}
SetNeedsCommit();
@ -685,18 +682,21 @@ void InputHandler::SetSynchronousInputHandlerRootScrollOffset(
root_content_offset - GetViewport().TotalScrollOffset();
physical_delta.Scale(ActiveTree().page_scale_factor_for_scroll());
bool changed = !GetViewport()
.ScrollBy(physical_delta,
/*viewport_point=*/gfx::Point(),
/*is_direct_manipulation=*/false,
/*affect_browser_controls=*/false,
/*scroll_outer_viewport=*/true)
.consumed_delta.IsZero();
if (!changed)
gfx::Vector2dF consumed_delta =
GetViewport()
.ScrollBy(physical_delta,
/*viewport_point=*/gfx::Point(),
/*is_direct_manipulation=*/false,
/*affect_browser_controls=*/false,
/*scroll_outer_viewport=*/true)
.consumed_delta;
if (consumed_delta.IsZero()) {
return;
}
compositor_delegate_->DidScrollContent(OuterViewportScrollNode()->element_id,
/*is_animated_scroll=*/false);
/*is_animated_scroll=*/false,
consumed_delta);
SetNeedsCommit();
// After applying the synchronous input handler's scroll offset, tell it what
@ -1058,8 +1058,8 @@ void InputHandler::ScrollEndForSnapFling(bool did_finish) {
ScrollEnd(true /* should_snap */);
}
void InputHandler::NotifyInputEvent() {
compositor_delegate_->NotifyInputEvent();
void InputHandler::NotifyInputEvent(bool is_fling) {
compositor_delegate_->NotifyInputEvent(is_fling);
}
//

@ -441,7 +441,7 @@ class CC_EXPORT InputHandler : public InputDelegateForCompositor {
// Notifies when any input event is received, irrespective of whether it is
// being handled by the InputHandler or not.
virtual void NotifyInputEvent();
virtual void NotifyInputEvent(bool is_fling);
// Returns true if ScrollbarController is in the middle of a scroll operation.
virtual bool ScrollbarScrollIsActive();

@ -74,6 +74,12 @@ const viz::BeginFrameArgs& BeginFrameTracker::Last() const {
return current_args_;
}
bool BeginFrameTracker::HasLast() const {
DCHECK(HasFinished())
<< "Tried to use last viz::BeginFrameArgs before the frame is finished.";
return current_args_.IsValid();
}
base::TimeDelta BeginFrameTracker::Interval() const {
base::TimeDelta interval = current_args_.interval;
// Normal interval will be ~16ms, 200Hz (5ms) screens are the fastest

@ -66,6 +66,7 @@ class CC_EXPORT BeginFrameTracker {
// **Must** only be called when **not** between the start and finish method
// calls.
const viz::BeginFrameArgs& Last() const;
bool HasLast() const;
// Helper method to try and return a valid interval property. Defaults to
// BFA::DefaultInterval() is no other interval can be found. Can be called at

@ -57,7 +57,7 @@ class FakeCompositorDelegateForInput : public CompositorDelegateForInput {
std::unique_ptr<EventsMetricsManager::ScopedMonitor>
GetScopedEventMetricsMonitor(
EventsMetricsManager::ScopedMonitor::DoneCallback done_callback) override;
void NotifyInputEvent() override {}
void NotifyInputEvent(bool is_fling) override {}
std::unique_ptr<LatencyInfoSwapPromiseMonitor>
CreateLatencyInfoSwapPromiseMonitor(ui::LatencyInfo* latency) override;
void SetNeedsAnimateInput() override {}
@ -69,7 +69,6 @@ class FakeCompositorDelegateForInput : public CompositorDelegateForInput {
void SetNeedsFullViewportRedraw() override {}
void SetDeferBeginMainFrame(bool defer_begin_main_frame) const override {}
void DidUpdateScrollAnimationCurve() override {}
void AccumulateScrollDeltaForTracing(const gfx::Vector2dF& delta) override {}
void DidStartPinchZoom() override {}
void DidUpdatePinchZoom() override {}
void DidEndPinchZoom() override {}
@ -78,7 +77,9 @@ class FakeCompositorDelegateForInput : public CompositorDelegateForInput {
void DidMouseLeave() override {}
bool IsInHighLatencyMode() const override;
void WillScrollContent(ElementId element_id) override {}
void DidScrollContent(ElementId element_id, bool animated) override {}
void DidScrollContent(ElementId element_id,
bool animated,
const gfx::Vector2dF& scroll_delta) override {}
float DeviceScaleFactor() const override;
float PageScaleFactor() const override;
gfx::Size VisualDeviceViewportSize() const override;

@ -62,7 +62,7 @@ class MockInputHandler : public InputHandler {
MOCK_METHOD1(MouseUp,
InputHandlerPointerResult(const gfx::PointF& mouse_position));
MOCK_METHOD1(SetIsHandlingTouchSequence, void(bool));
void NotifyInputEvent() override {}
void NotifyInputEvent(bool is_fling) override {}
std::unique_ptr<LatencyInfoSwapPromiseMonitor>
CreateLatencyInfoSwapPromiseMonitor(ui::LatencyInfo* latency) override {

@ -317,11 +317,6 @@ void LayerTreeHostImpl::DidUpdateScrollAnimationCurve() {
events_metrics_manager_.SaveActiveEventMetrics();
}
void LayerTreeHostImpl::AccumulateScrollDeltaForTracing(
const gfx::Vector2dF& delta) {
scroll_accumulated_this_frame_ += delta;
}
void LayerTreeHostImpl::DidStartPinchZoom() {
client_->RenewTreePriority();
frame_trackers_.StartSequence(FrameSequenceTrackerType::kPinchZoom);
@ -1188,8 +1183,9 @@ LayerTreeHostImpl::GetScopedEventMetricsMonitor(
return events_metrics_manager_.GetScopedMonitor(std::move(done_callback));
}
void LayerTreeHostImpl::NotifyInputEvent() {
void LayerTreeHostImpl::NotifyInputEvent(bool is_fling) {
frame_rate_estimator_.NotifyInputEvent();
has_non_fling_input_since_last_frame_ |= (!is_fling);
}
void LayerTreeHostImpl::QueueSwapPromiseForMainThreadScrollUpdate(
@ -3133,6 +3129,19 @@ viz::CompositorFrame LayerTreeHostImpl::GenerateCompositorFrame(
CurrentBeginFrameArgs().frame_time;
metadata.frame_interval_inputs.has_input =
frame_rate_estimator_.input_priority_mode();
has_non_fling_input_since_last_frame_ = false;
if (frame->damage_reasons.Has(DamageReason::kCompositorScroll)) {
// Sanity check frame time delta.
if (begin_frame_time_delta_.InMicroseconds() < 100 ||
begin_frame_time_delta_.InMicroseconds() > 1000000) {
metadata.frame_interval_inputs.major_scroll_speed_in_pixels_per_second =
0.f;
} else {
metadata.frame_interval_inputs.major_scroll_speed_in_pixels_per_second =
frame_max_scroll_delta_ / begin_frame_time_delta_.InSecondsF();
}
}
if (!frame->video_layer_preferred_intervals.empty() &&
frame->damage_reasons.Has(DamageReason::kVideoLayer)) {
@ -3445,6 +3454,15 @@ bool LayerTreeHostImpl::WillBeginImplFrame(const viz::BeginFrameArgs& args) {
input_delegate_->IsCurrentlyScrolling() &&
!input_delegate_->HasQueuedInput());
}
frame_max_scroll_delta_ = 0.f;
if (current_begin_frame_tracker_.HasLast()) {
begin_frame_time_delta_ =
args.frame_time - current_begin_frame_tracker_.Last().frame_time;
} else {
begin_frame_time_delta_ = base::TimeDelta();
}
impl_thread_phase_ = ImplThreadPhase::INSIDE_IMPL_FRAME;
current_begin_frame_tracker_.Start(args);
frame_trackers_.NotifyBeginImplFrame(args);
@ -4667,7 +4685,12 @@ void LayerTreeHostImpl::WillScrollContent(ElementId element_id) {
}
}
void LayerTreeHostImpl::DidScrollContent(ElementId element_id, bool animated) {
void LayerTreeHostImpl::DidScrollContent(ElementId element_id,
bool animated,
const gfx::Vector2dF& scroll_delta) {
scroll_accumulated_this_frame_ += scroll_delta;
frame_max_scroll_delta_ =
std::max(std::abs(scroll_delta.x()), std::abs(scroll_delta.y()));
if (settings().scrollbar_flash_after_any_scroll_update) {
FlashAllScrollbars(true);
} else {

@ -324,14 +324,13 @@ class CC_EXPORT LayerTreeHostImpl : public TileManagerClient,
std::unique_ptr<EventsMetricsManager::ScopedMonitor>
GetScopedEventMetricsMonitor(
EventsMetricsManager::ScopedMonitor::DoneCallback done_callback) override;
void NotifyInputEvent() override;
void NotifyInputEvent(bool is_fling) override;
bool HasAnimatedScrollbars() const override;
// Already overridden for BrowserControlsOffsetManagerClient which declares a
// method of the same name.
// void SetNeedsCommit();
void SetNeedsFullViewportRedraw() override;
void DidUpdateScrollAnimationCurve() override;
void AccumulateScrollDeltaForTracing(const gfx::Vector2dF& delta) override;
void DidStartPinchZoom() override;
void DidUpdatePinchZoom() override;
void DidEndPinchZoom() override;
@ -340,7 +339,9 @@ class CC_EXPORT LayerTreeHostImpl : public TileManagerClient,
void DidMouseLeave() override;
bool IsInHighLatencyMode() const override;
void WillScrollContent(ElementId element_id) override;
void DidScrollContent(ElementId element_id, bool animated) override;
void DidScrollContent(ElementId element_id,
bool animated,
const gfx::Vector2dF& scroll_delta) override;
float DeviceScaleFactor() const override;
float PageScaleFactor() const override;
gfx::Size VisualDeviceViewportSize() const override;
@ -1288,6 +1289,7 @@ class CC_EXPORT LayerTreeHostImpl : public TileManagerClient,
std::unique_ptr<LCDTextMetricsReporter> lcd_text_metrics_reporter_;
FrameRateEstimator frame_rate_estimator_;
bool has_non_fling_input_since_last_frame_ = false;
bool has_observed_first_scroll_delay_ = false;
// True if we are measuring smoothness in TotalFrameCounter and
@ -1312,6 +1314,13 @@ class CC_EXPORT LayerTreeHostImpl : public TileManagerClient,
float top_controls_visible_height_ = 0.f;
// Maximum scroll delta update in x or y direction since last begin impl
// frame.
float frame_max_scroll_delta_ = 0.f;
// Time delta between last and current begin impl frame.
base::TimeDelta begin_frame_time_delta_;
base::flat_set<ElementId> pending_invalidation_raster_inducing_scrolls_;
std::unordered_map<uint32_t, viz::ViewTransitionElementResourceRects>

@ -12084,7 +12084,7 @@ TEST_F(CommitToPendingTreeLayerTreeHostImplTest,
auto* fake_layer_tree_frame_sink =
static_cast<FakeLayerTreeFrameSink*>(host_impl_->layer_tree_frame_sink());
host_impl_->NotifyInputEvent();
host_impl_->NotifyInputEvent(/*is_fling=*/false);
host_impl_->SetFullViewportDamage();
host_impl_->SetNeedsRedraw();
auto args = viz::CreateBeginFrameArgsForTesting(
@ -18493,7 +18493,7 @@ TEST_P(LayerTreeHostImplTest, NonCompositedScrollUsesRaster) {
// Draw the next frame of the scroll.
{
host_impl_->NotifyInputEvent();
host_impl_->NotifyInputEvent(/*is_fling=*/false);
host_impl_->SetFullViewportDamage();
host_impl_->SetNeedsRedraw();
TestFrameData frame;

@ -40,6 +40,7 @@
#include "cc/trees/transform_node.h"
#include "components/viz/common/features.h"
#include "components/viz/common/frame_sinks/begin_frame_source.h"
#include "components/viz/common/quads/compositor_frame.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/events/types/scroll_input_type.h"
#include "ui/gfx/geometry/point_conversions.h"
@ -3263,5 +3264,59 @@ class CommitWithoutSynchronizingScrollOffsets : public LayerTreeHostScrollTest {
MULTI_THREAD_TEST_F(CommitWithoutSynchronizingScrollOffsets);
class LayerTreeHostScrollTestScrollFrameIntervalInputs
: public LayerTreeHostScrollTest {
public:
void BeginTest() override { PostSetNeedsCommitToMainThread(); }
void WillBeginImplFrameOnThread(LayerTreeHostImpl* host_impl,
const viz::BeginFrameArgs& args,
bool has_damage) override {
frame_time_delta_ = args.frame_time - last_frame_time_;
last_frame_time_ = args.frame_time;
if (has_activated_ && !has_scrolled_) {
// Scroll second frame.
ScrollStateData scroll_state_data;
scroll_state_data.is_beginning = true;
scroll_state_data.delta_y_hint = kScrollDelta;
ScrollState scroll_state(scroll_state_data);
host_impl->GetInputHandler().ScrollBegin(
&scroll_state, ui::ScrollInputType::kAutoscroll);
host_impl->GetInputHandler().ScrollUpdate(
UpdateState(gfx::Point(), gfx::Vector2d(0, kScrollDelta)));
has_scrolled_ = true;
}
}
void DidActivateTreeOnThread(LayerTreeHostImpl* host_impl) override {
has_activated_ = true;
}
void WillSubmitCompositorFrame(LayerTreeHostImpl* host_impl,
const viz::CompositorFrame& frame) override {
if (!has_scrolled_) {
host_impl->SetNeedsRedraw();
host_impl->SetFullViewportDamage();
} else {
int scroll_delta = kScrollDelta;
float pixels_per_second = scroll_delta / frame_time_delta_.InSecondsF();
EXPECT_EQ(pixels_per_second,
frame.metadata.frame_interval_inputs
.major_scroll_speed_in_pixels_per_second);
EndTest();
}
}
private:
constexpr static int kScrollDelta = 10;
bool has_activated_ = false;
bool has_scrolled_ = false;
base::TimeTicks last_frame_time_;
base::TimeDelta frame_time_delta_;
};
MULTI_THREAD_TEST_F(LayerTreeHostScrollTestScrollFrameIntervalInputs);
} // namespace
} // namespace cc

@ -61,6 +61,10 @@ struct VIZ_COMMON_EXPORT FrameIntervalInputs {
// as touch scrolling, in the future.
bool has_input = false;
// The maximum of x or y scroll speed.
// TODO(crbug.com/402442892): Serialize and use this.
float major_scroll_speed_in_pixels_per_second = 0.f;
// Any content that has a fixed or specified content frame interval can be
// added to `content_interval_info`. If `content_interval_info` contains
// information on _all_ updating content in this client , then client can

@ -322,8 +322,13 @@ void InputHandlerProxy::HandleInputEventWithLatencyInfo(
ChromeLatencyInfo2::Step::STEP_HANDLE_INPUT_EVENT_IMPL);
});
bool is_fling =
WebInputEvent::Type::kGestureScrollUpdate == event->Event().GetType() &&
static_cast<const WebGestureEvent&>(event->Event())
.data.scroll_update.inertial_phase ==
WebGestureEvent::InertialPhaseState::kMomentum;
DCHECK(input_handler_);
input_handler_->NotifyInputEvent();
input_handler_->NotifyInputEvent(is_fling);
// Prevent the events to be counted into INP metrics if there is an active
// scroll.