1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
|
// 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
|