xref: /aosp_15_r20/external/libchrome-gestures/src/accel_filter_interpreter.cc (revision aed3e5085e770be5b69ce25295ecf6ddf906af95)
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