1 // Copyright 2012 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "include/accel_filter_interpreter.h"
6
7 #include <algorithm>
8 #include <math.h>
9 #include <numeric>
10
11 #include "include/gestures.h"
12 #include "include/interpreter.h"
13 #include "include/logging.h"
14 #include "include/macros.h"
15 #include "include/tracer.h"
16
17 namespace gestures {
18
19 // Takes ownership of |next|:
AccelFilterInterpreter(PropRegistry * prop_reg,Interpreter * next,Tracer * tracer)20 AccelFilterInterpreter::AccelFilterInterpreter(PropRegistry* prop_reg,
21 Interpreter* next,
22 Tracer* tracer)
23 : FilterInterpreter(nullptr, next, tracer, false),
24 #pragma GCC diagnostic push
25 #pragma GCC diagnostic ignored "-Wsizeof-array-div"
26 // Hack: cast tp_custom_point_/mouse_custom_point_/tp_custom_scroll_
27 // to float arrays.
28 tp_custom_point_prop_(prop_reg, "Pointer Accel Curve",
29 reinterpret_cast<double*>(&tp_custom_point_),
30 sizeof(tp_custom_point_) / sizeof(double)),
31 tp_custom_scroll_prop_(prop_reg, "Scroll Accel Curve",
32 reinterpret_cast<double*>(&tp_custom_scroll_),
33 sizeof(tp_custom_scroll_) / sizeof(double)),
34 mouse_custom_point_prop_(prop_reg, "Mouse Pointer Accel Curve",
35 reinterpret_cast<double*>(&mouse_custom_point_),
36 sizeof(mouse_custom_point_) / sizeof(double)),
37 #pragma GCC diagnostic pop
38 use_custom_tp_point_curve_(
39 prop_reg, "Use Custom Touchpad Pointer Accel Curve", false),
40 use_custom_tp_scroll_curve_(
41 prop_reg, "Use Custom Touchpad Scroll Accel Curve", false),
42 use_custom_mouse_curve_(
43 prop_reg, "Use Custom Mouse Pointer Accel Curve", false),
44 pointer_sensitivity_(prop_reg, "Pointer Sensitivity", 3),
45 scroll_sensitivity_(prop_reg, "Scroll Sensitivity", 3),
46 point_x_out_scale_(prop_reg, "Point X Out Scale", 1.0),
47 point_y_out_scale_(prop_reg, "Point Y Out Scale", 1.0),
48 scroll_x_out_scale_(prop_reg, "Scroll X Out Scale", 2.5),
49 scroll_y_out_scale_(prop_reg, "Scroll Y Out Scale", 2.5),
50 use_mouse_point_curves_(prop_reg, "Mouse Accel Curves", false),
51 use_mouse_scroll_curves_(prop_reg, "Mouse Scroll Curves", false),
52 use_old_mouse_point_curves_(prop_reg, "Old Mouse Accel Curves", false),
53 pointer_acceleration_(prop_reg, "Pointer Acceleration", true),
54 min_reasonable_dt_(prop_reg, "Accel Min dt", 0.003),
55 max_reasonable_dt_(prop_reg, "Accel Max dt", 0.050),
56 smooth_accel_(prop_reg, "Smooth Accel", false) {
57 InitName();
58 // Set up default curves.
59
60 // Our pointing curves are the following.
61 // x = input speed of movement (mm/s, always >= 0), y = output speed (mm/s)
62 // 1: y = x (No acceleration)
63 // 2: y = 32x/60 (x < 32), x^2/60 (x < 150), linear with same slope after
64 // 3: y = 32x/37.5 (x < 32), x^2/37.5 (x < 150), linear with same slope after
65 // 4: y = 32x/30 (x < 32), x^2/30 (x < 150), linear with same slope after
66 // 5: y = 32x/25 (x < 32), x^2/25 (x < 150), linear with same slope after
67
68 const float point_divisors[] = { 0.0, // unused
69 60.0, 37.5, 30.0, 25.0 }; // used
70
71
72 // i starts as 1 b/c we skip the first slot, since the default is fine for it.
73 for (size_t i = 1; i < kMaxAccelCurves; ++i) {
74 const float divisor = point_divisors[i];
75 const float linear_until_x = 32.0;
76 const float init_slope = linear_until_x / divisor;
77 point_curves_[i][0] = CurveSegment(linear_until_x, 0, init_slope, 0);
78 const float x_border = 150;
79 point_curves_[i][1] = CurveSegment(x_border, 1 / divisor, 0, 0);
80 const float slope = x_border * 2 / divisor;
81 const float y_at_border = x_border * x_border / divisor;
82 const float icept = y_at_border - slope * x_border;
83 point_curves_[i][2] = CurveSegment(INFINITY, 0, slope, icept);
84 }
85
86 // Setup unaccelerated touchpad curves. Each one is just a single linear
87 // segment with the slope from |unaccel_tp_slopes|.
88 const float unaccel_tp_slopes[] = { 1.0, 2.0, 3.0, 4.0, 5.0 };
89 for (size_t i = 0; i < kMaxAccelCurves; ++i) {
90 unaccel_point_curves_[i] = CurveSegment(
91 INFINITY, 0, unaccel_tp_slopes[i], 0);
92 }
93
94 const float old_mouse_speed_straight_cutoff[] = { 5.0, 5.0, 5.0, 8.0, 8.0 };
95 const float old_mouse_speed_accel[] = { 1.0, 1.4, 1.8, 2.0, 2.2 };
96
97 for (size_t i = 0; i < kMaxAccelCurves; ++i) {
98 const float kParabolaA = 1.3;
99 const float kParabolaB = 0.2;
100 const float cutoff_x = old_mouse_speed_straight_cutoff[i];
101 const float cutoff_y =
102 kParabolaA * cutoff_x * cutoff_x + kParabolaB * cutoff_x;
103 const float line_m = 2.0 * kParabolaA * cutoff_x + kParabolaB;
104 const float line_b = cutoff_y - cutoff_x * line_m;
105 const float kOutMult = old_mouse_speed_accel[i];
106
107 old_mouse_point_curves_[i][0] =
108 CurveSegment(cutoff_x * 25.4, kParabolaA * kOutMult / 25.4,
109 kParabolaB * kOutMult, 0.0);
110 old_mouse_point_curves_[i][1] = CurveSegment(INFINITY, 0.0, line_m * kOutMult,
111 line_b * kOutMult * 25.4);
112 }
113
114 // These values were determined empirically through user studies:
115 const float kMouseMultiplierA = 0.0311;
116 const float kMouseMultiplierB = 3.26;
117 const float kMouseCutoff = 195.0;
118 const float kMultipliers[] = { 1.2, 1.4, 1.6, 1.8, 2.0 };
119 for (size_t i = 0; i < kMaxAccelCurves; ++i) {
120 float mouse_a = kMouseMultiplierA * kMultipliers[i] * kMultipliers[i];
121 float mouse_b = kMouseMultiplierB * kMultipliers[i];
122 float cutoff = kMouseCutoff / kMultipliers[i];
123 float second_slope =
124 (2.0 * kMouseMultiplierA * kMouseCutoff + kMouseMultiplierB) *
125 kMultipliers[i];
126 mouse_point_curves_[i][0] = CurveSegment(cutoff, mouse_a, mouse_b, 0.0);
127 mouse_point_curves_[i][1] = CurveSegment(INFINITY, 0.0, second_slope, -1182);
128 }
129
130 // Setup unaccelerated mouse curves. Each one is just a single linear
131 // segment with the slope from |unaccel_mouse_slopes|.
132 const float unaccel_mouse_slopes[] = { 2.0, 4.0, 8.0, 16.0, 24.0 };
133 for (size_t i = 0; i < kMaxAccelCurves; ++i) {
134 unaccel_mouse_curves_[i] = CurveSegment(
135 INFINITY, 0, unaccel_mouse_slopes[i], 0);
136 }
137
138 const float scroll_divisors[] = { 0.0, // unused
139 150, 75.0, 70.0, 65.0 }; // used
140 // Our scrolling curves are the following.
141 // x = input speed of movement (mm/s, always >= 0), y = output speed (mm/s)
142 // 1: y = x (No acceleration)
143 // 2: y = 75x/150 (x < 75), x^2/150 (x < 600), linear (initial slope).
144 // 3: y = 75x/75 (x < 75), x^2/75 (x < 600), linear (initial slope).
145 // 4: y = 75x/70 (x < 75), x^2/70 (x < 600), linear (initial slope).
146 // 5: y = 75x/65 (x < 75), x^2/65 (x < 600), linear (initial slope).
147 // i starts as 1 b/c we skip the first slot, since the default is fine for it.
148 for (size_t i = 1; i < kMaxAccelCurves; ++i) {
149 const float divisor = scroll_divisors[i];
150 const float linear_until_x = 75.0;
151 const float init_slope = linear_until_x / divisor;
152 scroll_curves_[i][0] = CurveSegment(linear_until_x, 0, init_slope, 0);
153 const float x_border = 600;
154 scroll_curves_[i][1] = CurveSegment(x_border, 1 / divisor, 0, 0);
155 // For scrolling / flinging we level off the speed.
156 const float slope = init_slope;
157 const float y_at_border = x_border * x_border / divisor;
158 const float icept = y_at_border - slope * x_border;
159 scroll_curves_[i][2] = CurveSegment(INFINITY, 0, slope, icept);
160 }
161 }
162
ConsumeGesture(const Gesture & gs)163 void AccelFilterInterpreter::ConsumeGesture(const Gesture& gs) {
164 const char name[] = "AccelFilterInterpreter::ConsumeGesture";
165 LogGestureConsume(name, gs);
166 auto debug_data = ActivityLog::AccelGestureDebug{};
167
168 // Use a copy of the gesture gs during the calculations and
169 // adjustments so the original is left alone.
170 Gesture gs_copy = gs;
171
172 // Setup the parameters for acceleration calculations based on gesture type.
173 float* dx;
174 float* dy;
175 float x_scale;
176 float y_scale;
177 float* scale_out_x;
178 float* scale_out_y;
179 float* scale_out_x_ordinal;
180 float* scale_out_y_ordinal;
181 size_t max_segs;
182 CurveSegment* segs;
183
184 if (!get_accel_parameters(gs_copy,
185 dx, dy,
186 x_scale, y_scale,
187 scale_out_x, scale_out_y,
188 scale_out_x_ordinal, scale_out_y_ordinal,
189 segs, max_segs)) {
190 // It was determined no acceleration was required.
191 debug_data.no_accel_for_gesture_type = true;
192 LogDebugData(debug_data);
193 LogGestureProduce(name, gs);
194 ProduceGesture(gs);
195 return;
196 }
197 debug_data.x_y_are_velocity = (dx == nullptr || dy == nullptr);
198 debug_data.x_scale = x_scale;
199 debug_data.y_scale = y_scale;
200 debug_data.dt = get_dt(gs);
201 debug_data.adjusted_dt = get_adjusted_dt(gs);
202
203 float speed;
204 if (!get_actual_speed(dx, dy,
205 gs.details.fling.vx, gs.details.fling.vy,
206 get_adjusted_dt(gs),
207 speed)) {
208 // dt was too small, don't accelerate.
209 debug_data.no_accel_for_small_dt = true;
210 LogDebugData(debug_data);
211 LogGestureProduce(name, gs);
212 ProduceGesture(gs);
213 return;
214 }
215 debug_data.speed = speed;
216 smooth_speed(gs, speed);
217 debug_data.smoothed_speed = speed;
218
219 // Avoid scaling if the speed is too small.
220 if (speed < 0.00001) {
221 debug_data.no_accel_for_small_speed = true;
222 if (gs.type != kGestureTypeFling)
223 debug_data.dropped_gesture = true;
224 LogDebugData(debug_data);
225 if (gs.type == kGestureTypeFling) {
226 LogGestureProduce(name, gs);
227 ProduceGesture(gs); // Filter out zero length gestures.
228 }
229 } else {
230 // Find the appropriate ratio and apply scaling.
231 auto ratio = RatioFromAccelCurve(segs, max_segs, speed);
232 debug_data.gain_x = ratio;
233 debug_data.gain_y = ratio;
234 if (ratio > 0.0) {
235 *scale_out_x *= ratio * x_scale;
236 *scale_out_y *= ratio * y_scale;
237
238 if (gs.type == kGestureTypeFling ||
239 gs.type == kGestureTypeScroll) {
240 // We don't accelerate the ordinal values as we do for normal ones
241 // because this is how the Chrome needs it.
242 *scale_out_x_ordinal *= x_scale;
243 *scale_out_y_ordinal *= y_scale;
244 }
245 LogDebugData(debug_data);
246 LogGestureProduce(name, gs_copy);
247 ProduceGesture(gs_copy);
248 } else {
249 debug_data.no_accel_for_bad_gain = true;
250 debug_data.dropped_gesture = true;
251 LogDebugData(debug_data);
252 }
253 }
254 }
255
get_dt(const Gesture & gs)256 float AccelFilterInterpreter::get_dt(const Gesture& gs) {
257 return gs.end_time - gs.start_time;
258 }
259
get_adjusted_dt(const Gesture & gs)260 float AccelFilterInterpreter::get_adjusted_dt(const Gesture& gs) {
261 float dt = get_dt(gs);
262
263 // If dt is not reasonable, use the last seen reasonable value
264 // Otherwise, save it as the last seen reasonable value
265 if (dt < min_reasonable_dt_.val_ || dt > max_reasonable_dt_.val_)
266 dt = last_reasonable_dt_;
267 else
268 last_reasonable_dt_ = dt;
269
270 return dt;
271 }
272
get_accel_parameters(Gesture & gs,float * & dx,float * & dy,float & x_scale,float & y_scale,float * & scale_out_x,float * & scale_out_y,float * & scale_out_x_ordinal,float * & scale_out_y_ordinal,CurveSegment * & segs,size_t & max_segs)273 bool AccelFilterInterpreter::get_accel_parameters(
274 Gesture& gs,
275 float*& dx, float*& dy,
276 float& x_scale, float& y_scale,
277 float*& scale_out_x, float*& scale_out_y,
278 float*& scale_out_x_ordinal, float*& scale_out_y_ordinal,
279 CurveSegment*& segs, size_t& max_segs) {
280 // CurveSegments to use.
281 max_segs = kMaxCurveSegs;
282 segs = nullptr;
283
284 dx = nullptr;
285 dy = nullptr;
286
287 // The quantities to scale.
288 scale_out_x = nullptr;
289 scale_out_y = nullptr;
290
291 // We scale ordinal values of scroll/fling gestures as well because we use
292 // them in Chrome for history navigation (back/forward page gesture) and
293 // we will easily run out of the touchpad space if we just use raw values
294 // as they are. To estimate the length one needs to scroll on the touchpad
295 // to trigger the history navigation:
296 //
297 // Pixel:
298 // 1280 (screen width in DIPs) * 0.25 (overscroll threshold) /
299 // (133 / 25.4) (conversion factor from DIP to mm) = 61.1 mm
300 // Most other low-res devices:
301 // 1366 * 0.25 / (133 / 25.4) = 65.2 mm
302 //
303 // With current scroll output scaling factor (2.5), we can reduce the length
304 // required to about one inch on all devices.
305 scale_out_x_ordinal = nullptr;
306 scale_out_y_ordinal = nullptr;
307
308 // Setup the parameters for acceleration calculations based on gesture
309 // type.
310 switch (gs.type) {
311 case kGestureTypeMove:
312 case kGestureTypeSwipe:
313 case kGestureTypeFourFingerSwipe:
314 // Setup the Gesture delta fields/scaling for the gesture type.
315 if (gs.type == kGestureTypeMove) {
316 scale_out_x = dx = &gs.details.move.dx;
317 scale_out_y = dy = &gs.details.move.dy;
318 } else if (gs.type == kGestureTypeSwipe) {
319 scale_out_x = dx = &gs.details.swipe.dx;
320 scale_out_y = dy = &gs.details.swipe.dy;
321 } else {
322 scale_out_x = dx = &gs.details.four_finger_swipe.dx;
323 scale_out_y = dy = &gs.details.four_finger_swipe.dy;
324 }
325
326 // Setup CurveSegments for the device options set.
327 if (use_mouse_point_curves_.val_ && use_custom_mouse_curve_.val_) {
328 // Custom Mouse.
329 segs = mouse_custom_point_;
330 max_segs = kMaxCustomCurveSegs;
331 } else if (!use_mouse_point_curves_.val_ &&
332 use_custom_tp_point_curve_.val_) {
333 // Custom Touch.
334 segs = tp_custom_point_;
335 max_segs = kMaxCustomCurveSegs;
336 } else if (use_mouse_point_curves_.val_) {
337 // Standard Mouse.
338 if (!pointer_acceleration_.val_) {
339 segs = &unaccel_mouse_curves_[pointer_sensitivity_.val_ - 1];
340 max_segs = kMaxUnaccelCurveSegs;
341 } else if (use_old_mouse_point_curves_.val_) {
342 segs = old_mouse_point_curves_[pointer_sensitivity_.val_ - 1];
343 } else {
344 segs = mouse_point_curves_[pointer_sensitivity_.val_ - 1];
345 }
346 } else {
347 // Standard Touch.
348 if (!pointer_acceleration_.val_) {
349 segs = &unaccel_point_curves_[pointer_sensitivity_.val_ - 1];
350 max_segs = kMaxUnaccelCurveSegs;
351 } else {
352 segs = point_curves_[pointer_sensitivity_.val_ - 1];
353 }
354 }
355
356 x_scale = point_x_out_scale_.val_;
357 y_scale = point_y_out_scale_.val_;
358 break;
359
360 case kGestureTypeFling:
361 case kGestureTypeScroll:
362 // We bypass mouse scroll events as they have a separate acceleration
363 // algorithm implemented in mouse_interpreter.
364 if (use_mouse_scroll_curves_.val_)
365 return false;
366
367 // Setup the Gesture velocity/delta fields/scaling for the gesture type.
368 if (gs.type == kGestureTypeFling) {
369 scale_out_x = &gs.details.fling.vx;
370 scale_out_y = &gs.details.fling.vy;
371 scale_out_x_ordinal = &gs.details.fling.ordinal_vx;
372 scale_out_y_ordinal = &gs.details.fling.ordinal_vy;
373 } else {
374 scale_out_x = dx = &gs.details.scroll.dx;
375 scale_out_y = dy = &gs.details.scroll.dy;
376 scale_out_x_ordinal = &gs.details.scroll.ordinal_dx;
377 scale_out_y_ordinal = &gs.details.scroll.ordinal_dy;
378 }
379
380 // Setup CurveSegments for the device options set.
381 if (!use_custom_tp_scroll_curve_.val_) {
382 segs = scroll_curves_[scroll_sensitivity_.val_ - 1];
383 } else {
384 segs = tp_custom_scroll_;
385 max_segs = kMaxCustomCurveSegs;
386 }
387
388 x_scale = scroll_x_out_scale_.val_;
389 y_scale = scroll_y_out_scale_.val_;
390 break;
391
392 default: // Nothing to accelerate.
393 return false;
394 }
395 return true;
396 }
397
get_actual_speed(float * dx,float * dy,float vx,float vy,float dt,float & speed)398 bool AccelFilterInterpreter::get_actual_speed(
399 float* dx, float* dy,
400 float vx, float vy,
401 float dt,
402 float& speed) {
403 // Calculate the hypotenuse to determine the actual speed.
404 if (dx != nullptr && dy != nullptr) {
405 if (dt < 0.00001)
406 return false; // Avoid division by 0.
407 speed = sqrtf(*dx * *dx + *dy * *dy) / dt;
408 } else {
409 // FLING is the only gesture that uses vx/vy and assumes dt=1.
410 speed = sqrtf(vx * vx + vy * vy);
411 }
412 return true;
413 }
414
smooth_speed(const Gesture & gs,float & speed)415 void AccelFilterInterpreter::smooth_speed(const Gesture& gs, float& speed) {
416 // Perform smoothing, if it is enabled.
417 if (smooth_accel_.val_) {
418 // Check if clock changed backwards.
419 if (last_end_time_ > gs.start_time)
420 last_end_time_ = -1.0;
421
422 if (last_end_time_ == gs.start_time) {
423 // Average the saved magnitudes.
424 last_mags_.insert(last_mags_.begin(), speed);
425 speed = std::reduce(last_mags_.begin(), last_mags_.end()) /
426 last_mags_.size();
427 // Limit the size of last_mags_ to the needed oldest mag entries.
428 if (last_mags_.size() > kMaxLastMagsSize)
429 last_mags_.pop_back();
430 } else {
431 // Time stamp jump, start smoothing anew.
432 last_mags_.clear();
433 last_mags_.push_back(speed);
434 }
435 last_end_time_ = gs.end_time;
436 }
437 }
438
RatioFromAccelCurve(const CurveSegment * segs,const size_t max_segs,const float speed)439 float AccelFilterInterpreter::RatioFromAccelCurve(
440 const CurveSegment* segs,
441 const size_t max_segs,
442 const float speed) {
443 if (speed <= 0.0)
444 return 0.0;
445
446 for (size_t i = 0; i < max_segs; ++i) {
447 const CurveSegment& seg = segs[i];
448 if (speed <= seg.x_) {
449 return (seg.sqr_ * speed) + seg.mul_ + (seg.int_ / speed);
450 }
451 }
452 return 0.0;
453 }
454
455 } // namespace gestures
456