blob: 26588274c81ce15535a0a181a9a896a4db09e914 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/base/prediction/linear_resampling.h"
#include <algorithm>
#include "base/feature_list.h"
#include "base/metrics/field_trial_params.h"
#include "base/strings/string_number_conversions.h"
#include "base/trace_event/trace_event.h"
#include "ui/base/ui_base_features.h"
namespace ui {
namespace {
// Minimum time difference between last two consecutive events before attempting
// to resample.
constexpr auto kResampleMinDelta = base::Milliseconds(2);
// Maximum time to predict forward from the last event, to avoid predicting too
// far into the future. This time is further bounded by 50% of the last time
// delta.
constexpr auto kResampleMaxPrediction = base::Milliseconds(8);
// Align events to a few milliseconds before frame_time. This is to make the
// resampling either doing interpolation or extrapolating a closer future time
// so that resampled result is more accurate and has less noise. This adds some
// latency during resampling but a few ms should be fine.
constexpr auto kResampleLatency = base::Milliseconds(-5);
// The optimal prediction anticipation from experimentation: In the study
// https://bit.ly/3iyQf8V we found that, on a machine with VSync at 60Hz, adding
// 1/2 * frame_interval (on top of kResampleLatency) minimizes the Lag on touch
// scrolling. + 1/2 * (1/60) - 5ms = 3.3ms.
constexpr auto kResampleLatencyExperimental = base::Milliseconds(3.3);
// Get position at |sample_time| by linear interpolate/extrapolate a and b.
inline gfx::PointF lerp(const InputPredictor::InputData& a,
const InputPredictor::InputData& b,
base::TimeTicks sample_time) {
const float alpha =
(sample_time - a.time_stamp) / (a.time_stamp - b.time_stamp);
return a.pos + gfx::ScaleVector2d(a.pos - b.pos, alpha);
}
} // namespace
LinearResampling::LinearResampling() = default;
LinearResampling::~LinearResampling() = default;
const char* LinearResampling::GetName() const {
return features::kPredictorNameLinearResampling;
}
void LinearResampling::Reset() {
events_queue_.clear();
}
void LinearResampling::Update(const InputData& new_input) {
// The last input received is at least kMaxDeltaTime away, we consider it
// is a new trajectory
if (!events_queue_.empty() &&
new_input.time_stamp - events_queue_.front().time_stamp > kMaxTimeDelta) {
Reset();
}
// Queue the new event.
events_queue_.push_front(new_input);
if (events_queue_.size() > kNumEventsForResampling)
events_queue_.pop_back();
DCHECK(events_queue_.size() <= kNumEventsForResampling);
if (events_queue_.size() == kNumEventsForResampling)
events_dt_ = events_queue_[0].time_stamp - events_queue_[1].time_stamp;
}
bool LinearResampling::HasPrediction() const {
return events_queue_.size() == kNumEventsForResampling &&
events_dt_ >= kResampleMinDelta;
}
std::unique_ptr<InputPredictor::InputData> LinearResampling::GeneratePrediction(
base::TimeTicks frame_time,
base::TimeDelta frame_interval) {
if (!HasPrediction())
return nullptr;
base::TimeDelta resample_latency =
latency_calculator_.GetResampleLatency(frame_interval);
base::TimeTicks sample_time = frame_time + resample_latency;
// Clamping shouldn't affect prediction experiment, as we're predicting
// further in the future.
if (!base::FeatureList::IsEnabled(
::features::kResamplingScrollEventsExperimentalPrediction)) {
base::TimeDelta max_prediction =
std::min(kResampleMaxPrediction, events_dt_ / 2.0);
sample_time =
std::min(sample_time, events_queue_[0].time_stamp + max_prediction);
}
return std::make_unique<InputData>(
lerp(events_queue_[0], events_queue_[1], sample_time), sample_time);
}
base::TimeDelta LinearResampling::TimeInterval() const {
if (events_queue_.size() == kNumEventsForResampling) {
return events_dt_;
}
return kTimeInterval;
}
base::TimeDelta LinearResampling::LatencyCalculator::GetResampleLatency(
base::TimeDelta frame_interval) {
// Cache |resample_latency_| and recalculate only when |frame_interval|
// changes.
if (frame_interval != frame_interval_ || resample_latency_.is_zero()) {
frame_interval_ = frame_interval;
resample_latency_ = CalculateLatency();
}
return resample_latency_;
}
base::TimeDelta LinearResampling::LatencyCalculator::CalculateLatency() {
std::string prediction_type = GetFieldTrialParamValueByFeature(
::features::kResamplingScrollEventsExperimentalPrediction, "mode");
std::string latency_value = GetFieldTrialParamValueByFeature(
::features::kResamplingScrollEventsExperimentalPrediction, "latency");
TRACE_EVENT2("ui", "LatencyCalculator::CalculateLatency", "prediction_type",
prediction_type, "latency_value", latency_value);
if (prediction_type != ::features::kPredictionTypeTimeBased &&
prediction_type != ::features::kPredictionTypeFramesBased)
return kResampleLatency;
double latency;
if (base::StringToDouble(latency_value, &latency)) {
return prediction_type == ::features::kPredictionTypeTimeBased
? base::Milliseconds(latency)
: latency * frame_interval_ + kResampleLatency;
}
return prediction_type == ::features::kPredictionTypeTimeBased
? kResampleLatencyExperimental
: 0.5 * frame_interval_ + kResampleLatency;
}
} // namespace ui