Commit bdd4264d authored by Peter Hutterer's avatar Peter Hutterer

filter: change the filter functions to take raw device coordinates

We used to normalize all deltas to equivalents of a 1000dpi mouse before
passing it into the acceleration functions. This has a bunch of drawbacks, not
least that we already have to un-normalize back into device units for a few
devices already (trackpoints, tablet, low-dpi mice).

Switch the filter code over to use device units, relying on the dpi set
earlier during filter creation to convert to normalized. To make things easy,
the output of the filter code is still normalized data, i.e. data ready to be
handed to the libinput caller.

No effective functional changes. For touchpads, we still send normalized
coordinates (for now, anyway). For the various filter methods, we either drop
the places where we unnormalized before or we normalize where needed.

Two possible changes: for trackpoints and low-dpi mice we had a max dpi factor
of 1.0 before - now we don't anymore. This was only the case if a low-dpi
mouse had more than 1000dpi (never true) or a trackpoint had a const accel
lower than 1.0 (yeah, whatever).
Signed-off-by: Peter Hutterer's avatarPeter Hutterer <peter.hutterer@who-t.net>
Reviewed-by: default avatarHans de Goede <hdegoede@redhat.com>
parent 187c38f6
......@@ -51,11 +51,17 @@ tp_filter_motion(struct tp_dispatch *tp,
const struct normalized_coords *unaccelerated,
uint64_t time)
{
struct device_float_coords raw;
if (normalized_is_zero(*unaccelerated))
return *unaccelerated;
/* Temporary solution only: convert back to raw coordinates, but
* make sure we're on the same resolution for both axes */
raw = tp_unnormalize_for_xaxis(tp, *unaccelerated);
return filter_dispatch(tp->device->pointer.filter,
unaccelerated, tp, time);
&raw, tp, time);
}
struct normalized_coords
......@@ -63,11 +69,17 @@ tp_filter_motion_unaccelerated(struct tp_dispatch *tp,
const struct normalized_coords *unaccelerated,
uint64_t time)
{
struct device_float_coords raw;
if (normalized_is_zero(*unaccelerated))
return *unaccelerated;
/* Temporary solution only: convert back to raw coordinates, but
* make sure we're on the same resolution for both axes */
raw = tp_unnormalize_for_xaxis(tp, *unaccelerated);
return filter_dispatch_constant(tp->device->pointer.filter,
unaccelerated, tp, time);
&raw, tp, time);
}
static inline void
......
......@@ -392,13 +392,14 @@ tool_process_delta(struct libinput_tablet_tool *tool,
const struct device_coords *delta,
uint64_t time)
{
struct normalized_coords accel;
const struct normalized_coords zero = { 0.0, 0.0 };
struct device_float_coords accel;
accel.x = 1.0 * delta->x;
accel.y = 1.0 * delta->y;
if (normalized_is_zero(accel))
return accel;
if (device_float_is_zero(accel))
return zero;
return filter_dispatch(device->pointer.filter,
&accel,
......
......@@ -419,7 +419,7 @@ fallback_flush_relative_motion(struct fallback_dispatch *dispatch,
if (device->pointer.filter) {
/* Apply pointer acceleration. */
accel = filter_dispatch(device->pointer.filter,
&unaccel,
&raw,
device,
time);
} else {
......
......@@ -32,11 +32,11 @@ struct motion_filter_interface {
enum libinput_config_accel_profile type;
struct normalized_coords (*filter)(
struct motion_filter *filter,
const struct normalized_coords *unaccelerated,
const struct device_float_coords *unaccelerated,
void *data, uint64_t time);
struct normalized_coords (*filter_constant)(
struct motion_filter *filter,
const struct normalized_coords *unaccelerated,
const struct device_float_coords *unaccelerated,
void *data, uint64_t time);
void (*restart)(struct motion_filter *filter,
void *data,
......
......@@ -56,9 +56,20 @@ v_ms2us(double units_per_ms)
return units_per_ms/1000.0;
}
static inline struct normalized_coords
normalize_for_dpi(const struct device_float_coords *coords, int dpi)
{
struct normalized_coords norm;
norm.x = coords->x * DEFAULT_MOUSE_DPI/dpi;
norm.y = coords->y * DEFAULT_MOUSE_DPI/dpi;
return norm;
}
struct normalized_coords
filter_dispatch(struct motion_filter *filter,
const struct normalized_coords *unaccelerated,
const struct device_float_coords *unaccelerated,
void *data, uint64_t time)
{
return filter->interface->filter(filter, unaccelerated, data, time);
......@@ -66,7 +77,7 @@ filter_dispatch(struct motion_filter *filter,
struct normalized_coords
filter_dispatch_constant(struct motion_filter *filter,
const struct normalized_coords *unaccelerated,
const struct device_float_coords *unaccelerated,
void *data, uint64_t time)
{
return filter->interface->filter_constant(filter, unaccelerated, data, time);
......@@ -180,7 +191,7 @@ struct tablet_accelerator_flat {
static void
feed_trackers(struct pointer_accelerator *accel,
const struct normalized_coords *delta,
const struct device_float_coords *delta,
uint64_t time)
{
int i, current;
......@@ -197,7 +208,7 @@ feed_trackers(struct pointer_accelerator *accel,
trackers[current].delta.x = 0.0;
trackers[current].delta.y = 0.0;
trackers[current].time = time;
trackers[current].dir = normalized_get_direction(*delta);
trackers[current].dir = device_float_get_direction(*delta);
}
static struct pointer_tracker *
......@@ -296,6 +307,16 @@ calculate_velocity(struct pointer_accelerator *accel, uint64_t time)
return result; /* units/us */
}
/**
* Apply the acceleration profile to the given velocity.
*
* @param accel The acceleration filter
* @param data Caller-specific data
* @param velocity Velocity in device-units per µs
* @param time Current time in µs
*
* @return A unitless acceleration factor, to be applied to the delta
*/
static double
acceleration_profile(struct pointer_accelerator *accel,
void *data, double velocity, uint64_t time)
......@@ -303,6 +324,18 @@ acceleration_profile(struct pointer_accelerator *accel,
return accel->profile(&accel->base, data, velocity, time);
}
/**
* Calculate the acceleration factor for our current velocity, averaging
* between our current and the most recent velocity to smoothen out changes.
*
* @param accel The acceleration filter
* @param data Caller-specific data
* @param velocity Velocity in device-units per µs
* @param last_velocity Previous velocity in device-units per µs
* @param time Current time in µs
*
* @return A unitless acceleration factor, to be applied to the delta
*/
static double
calculate_acceleration(struct pointer_accelerator *accel,
void *data,
......@@ -326,13 +359,23 @@ calculate_acceleration(struct pointer_accelerator *accel,
return factor; /* unitless factor */
}
/**
* Calculate the acceleration factor for the given delta with the timestamp.
*
* @param accel The acceleration filter
* @param unaccelerated The raw delta in the device's dpi
* @param data Caller-specific data
* @param time Current time in µs
*
* @return A unitless acceleration factor, to be applied to the delta
*/
static inline double
calculate_acceleration_factor(struct pointer_accelerator *accel,
const struct normalized_coords *unaccelerated,
const struct device_float_coords *unaccelerated,
void *data,
uint64_t time)
{
double velocity; /* units/us */
double velocity; /* units/us in device-native dpi*/
double accel_factor;
feed_trackers(accel, unaccelerated, time);
......@@ -347,9 +390,21 @@ calculate_acceleration_factor(struct pointer_accelerator *accel,
return accel_factor;
}
/**
* Generic filter that calculates the acceleration factor and applies it to
* the coordinates.
*
* @param filter The acceleration filter
* @param unaccelerated The raw delta in the device's dpi
* @param data Caller-specific data
* @param time Current time in µs
*
* @return An accelerated tuple of coordinates representing normalized
* motion
*/
static struct normalized_coords
accelerator_filter(struct motion_filter *filter,
const struct normalized_coords *unaccelerated,
const struct device_float_coords *unaccelerated,
void *data, uint64_t time)
{
struct pointer_accelerator *accel =
......@@ -368,85 +423,120 @@ accelerator_filter(struct motion_filter *filter,
return accelerated;
}
/**
* Generic filter that does nothing beyond converting from the device's
* native dpi into normalized coordinates.
*
* @param filter The acceleration filter
* @param unaccelerated The raw delta in the device's dpi
* @param data Caller-specific data
* @param time Current time in µs
*
* @return An accelerated tuple of coordinates representing normalized
* motion
*/
static struct normalized_coords
accelerator_filter_noop(struct motion_filter *filter,
const struct normalized_coords *unaccelerated,
const struct device_float_coords *unaccelerated,
void *data, uint64_t time)
{
return *unaccelerated;
struct pointer_accelerator *accel =
(struct pointer_accelerator *) filter;
return normalize_for_dpi(unaccelerated, accel->dpi);
}
/**
* Low-dpi filter that handles events from devices with less than the
* default dpi.
*
* @param filter The acceleration filter
* @param unaccelerated The raw delta in the device's dpi
* @param data Caller-specific data
* @param time Current time in µs
*
* @return An accelerated tuple of coordinates representing normalized
* motion
*/
static struct normalized_coords
accelerator_filter_low_dpi(struct motion_filter *filter,
const struct normalized_coords *unaccelerated,
const struct device_float_coords *unaccelerated,
void *data, uint64_t time)
{
struct pointer_accelerator *accel =
(struct pointer_accelerator *) filter;
double accel_value; /* unitless factor */
struct normalized_coords accelerated;
struct normalized_coords unnormalized;
double dpi_factor = accel->dpi/(double)DEFAULT_MOUSE_DPI;
/* For low-dpi mice, use device units, everything else uses
1000dpi normalized */
dpi_factor = min(1.0, dpi_factor);
unnormalized.x = unaccelerated->x * dpi_factor;
unnormalized.y = unaccelerated->y * dpi_factor;
/* Input is already in device-native DPI, nothing else needed */
accel_value = calculate_acceleration_factor(accel,
&unnormalized,
unaccelerated,
data,
time);
accelerated.x = accel_value * unnormalized.x;
accelerated.y = accel_value * unnormalized.y;
accelerated.x = accel_value * unaccelerated->x;
accelerated.y = accel_value * unaccelerated->y;
return accelerated;
}
/**
* Custom filter that applies the trackpoint's constant acceleration, if any.
*
* @param filter The acceleration filter
* @param unaccelerated The raw delta in the device's dpi
* @param data Caller-specific data
* @param time Current time in µs
*
* @return An accelerated tuple of coordinates representing normalized
* motion
*/
static struct normalized_coords
accelerator_filter_trackpoint(struct motion_filter *filter,
const struct normalized_coords *unaccelerated,
const struct device_float_coords *unaccelerated,
void *data, uint64_t time)
{
struct pointer_accelerator *accel =
(struct pointer_accelerator *) filter;
double accel_value; /* unitless factor */
struct normalized_coords accelerated;
struct normalized_coords unnormalized;
double dpi_factor = accel->dpi/(double)DEFAULT_MOUSE_DPI;
/* trackpoints with a dpi factor have a const accel set, remove that
* and restore device units. The accel profile takes const accel
* into account */
dpi_factor = min(1.0, dpi_factor);
unnormalized.x = unaccelerated->x * dpi_factor;
unnormalized.y = unaccelerated->y * dpi_factor;
/* Nothing special to do here, data is already in device dpi */
accel_value = calculate_acceleration_factor(accel,
&unnormalized,
unaccelerated,
data,
time);
accelerated.x = accel_value * unnormalized.x;
accelerated.y = accel_value * unnormalized.y;
accelerated.x = accel_value * unaccelerated->x;
accelerated.y = accel_value * unaccelerated->y;
return accelerated;
}
static struct normalized_coords
accelerator_filter_x230(struct motion_filter *filter,
const struct normalized_coords *unaccelerated,
const struct device_float_coords *raw,
void *data, uint64_t time)
{
struct pointer_accelerator *accel =
(struct pointer_accelerator *) filter;
double accel_factor; /* unitless factor */
struct normalized_coords accelerated;
struct device_float_coords delta_normalized;
struct normalized_coords unaccelerated;
double velocity; /* units/us */
feed_trackers(accel, unaccelerated, time);
/* This filter is a "do not touch me" filter. So the hack here is
* just to replicate the old behavior before filters switched to
* device-native dpi:
* 1) convert from device-native to 1000dpi normalized
* 2) run all calculation on 1000dpi-normalized data
* 3) apply accel factor no normalized data
*/
unaccelerated = normalize_for_dpi(raw, accel->dpi);
delta_normalized.x = unaccelerated.x;
delta_normalized.y = unaccelerated.y;
feed_trackers(accel, &delta_normalized, time);
velocity = calculate_velocity(accel, time);
accel_factor = calculate_acceleration(accel,
data,
......@@ -455,23 +545,26 @@ accelerator_filter_x230(struct motion_filter *filter,
time);
accel->last_velocity = velocity;
accelerated.x = accel_factor * unaccelerated->x;
accelerated.y = accel_factor * unaccelerated->y;
accelerated.x = accel_factor * delta_normalized.x;
accelerated.y = accel_factor * delta_normalized.y;
return accelerated;
}
static struct normalized_coords
accelerator_filter_constant_x230(struct motion_filter *filter,
const struct normalized_coords *unaccelerated,
const struct device_float_coords *unaccelerated,
void *data, uint64_t time)
{
struct pointer_accelerator *accel =
(struct pointer_accelerator *) filter;
struct normalized_coords normalized;
const double factor =
X230_MAGIC_SLOWDOWN/X230_TP_MAGIC_LOW_RES_FACTOR;
normalized.x = factor * unaccelerated->x;
normalized.y = factor * unaccelerated->y;
normalized = normalize_for_dpi(unaccelerated, accel->dpi);
normalized.x = factor * normalized.x;
normalized.y = factor * normalized.y;
return normalized;
}
......@@ -506,13 +599,16 @@ touchpad_accelerator_set_speed(struct motion_filter *filter,
static struct normalized_coords
touchpad_constant_filter(struct motion_filter *filter,
const struct normalized_coords *unaccelerated,
const struct device_float_coords *unaccelerated,
void *data, uint64_t time)
{
struct pointer_accelerator *accel =
(struct pointer_accelerator *)filter;
struct normalized_coords normalized;
normalized.x = TP_MAGIC_SLOWDOWN * unaccelerated->x;
normalized.y = TP_MAGIC_SLOWDOWN * unaccelerated->y;
normalized = normalize_for_dpi(unaccelerated, accel->dpi);
normalized.x = TP_MAGIC_SLOWDOWN * normalized.x;
normalized.y = TP_MAGIC_SLOWDOWN * normalized.y;
return normalized;
}
......@@ -624,7 +720,7 @@ pointer_accel_profile_linear_low_dpi(struct motion_filter *filter,
double
pointer_accel_profile_linear(struct motion_filter *filter,
void *data,
double speed_in, /* 1000-dpi normalized */
double speed_in, /* in device units (units/µs) */
uint64_t time)
{
struct pointer_accelerator *accel_filter =
......@@ -634,6 +730,9 @@ pointer_accel_profile_linear(struct motion_filter *filter,
const double incline = accel_filter->incline;
double factor; /* unitless */
/* Normalize to 1000dpi, because the rest below relies on that */
speed_in = speed_in * DEFAULT_MOUSE_DPI/accel_filter->dpi;
/*
Our acceleration function calculates a factor to accelerate input
deltas with. The function is a double incline with a plateau,
......@@ -697,7 +796,7 @@ pointer_accel_profile_linear(struct motion_filter *filter,
double
touchpad_accel_profile_linear(struct motion_filter *filter,
void *data,
double speed_in, /* 1000-dpi normalized */
double speed_in, /* in device units/µs */
uint64_t time)
{
struct pointer_accelerator *accel_filter =
......@@ -707,6 +806,9 @@ touchpad_accel_profile_linear(struct motion_filter *filter,
const double incline = accel_filter->incline;
double factor; /* unitless */
/* Normalize to 1000dpi, because the rest below relies on that */
speed_in = speed_in * DEFAULT_MOUSE_DPI/accel_filter->dpi;
speed_in *= TP_MAGIC_SLOWDOWN;
/*
......@@ -771,7 +873,7 @@ touchpad_accel_profile_linear(struct motion_filter *filter,
double
touchpad_lenovo_x230_accel_profile(struct motion_filter *filter,
void *data,
double speed_in,
double speed_in, /* 1000dpi-units/µs */
uint64_t time)
{
/* Those touchpads presents an actual lower resolution that what is
......@@ -1002,24 +1104,19 @@ create_pointer_accelerator_filter_trackpoint(int dpi)
static struct normalized_coords
accelerator_filter_flat(struct motion_filter *filter,
const struct normalized_coords *unaccelerated,
const struct device_float_coords *unaccelerated,
void *data, uint64_t time)
{
struct pointer_accelerator_flat *accel_filter =
(struct pointer_accelerator_flat *)filter;
double factor; /* unitless factor */
struct normalized_coords accelerated;
struct normalized_coords unnormalized;
double dpi_factor = accel_filter->dpi/(double)DEFAULT_MOUSE_DPI;
/* You want flat acceleration, you get flat acceleration for the
* device */
unnormalized.x = unaccelerated->x * dpi_factor;
unnormalized.y = unaccelerated->y * dpi_factor;
factor = accel_filter->factor;
accelerated.x = factor * unnormalized.x;
accelerated.y = factor * unnormalized.y;
accelerated.x = factor * unaccelerated->x;
accelerated.y = factor * unaccelerated->y;
return accelerated;
}
......@@ -1079,19 +1176,17 @@ create_pointer_accelerator_filter_flat(int dpi)
static inline struct normalized_coords
tablet_accelerator_filter_flat_mouse(struct tablet_accelerator_flat *filter,
const struct normalized_coords *units)
const struct device_float_coords *units)
{
struct normalized_coords accelerated;
/*
The input for and output of accel methods is usually a delta in
1000dpi equivalents. Tablets are high res (Intuos 4 is 5080 dpi)
and unmodified deltas are way too high. Slow it down to the
equivalent of a 1000dpi mouse. The ratio of that is:
Tablets are high res (Intuos 4 is 5080 dpi) and unmodified deltas
are way too high. Slow it down to the equivalent of a 1000dpi
mouse. The ratio of that is:
ratio = 1000/(resolution_per_mm * 25.4)
i.e. on the Intuos4 it's a ratio of ~1/5.
*/
accelerated.x = units->x * filter->xres_scale;
......@@ -1105,12 +1200,12 @@ tablet_accelerator_filter_flat_mouse(struct tablet_accelerator_flat *filter,
static struct normalized_coords
tablet_accelerator_filter_flat_pen(struct tablet_accelerator_flat *filter,
const struct normalized_coords *units)
const struct device_float_coords *units)
{
struct normalized_coords accelerated;
/* Tablet input is in device units, output is supposed to be in logical
* pixels roughly equivalent to a mouse/touchpad.
/* Tablet input is in device units, output is supposed to be in
* logical pixels roughly equivalent to a mouse/touchpad.
*
* This is a magical constant found by trial and error. On a 96dpi
* screen 0.4mm of movement correspond to 1px logical pixel which
......@@ -1130,7 +1225,7 @@ tablet_accelerator_filter_flat_pen(struct tablet_accelerator_flat *filter,
static struct normalized_coords
tablet_accelerator_filter_flat(struct motion_filter *filter,
const struct normalized_coords *units,
const struct device_float_coords *units,
void *data, uint64_t time)
{
struct tablet_accelerator_flat *accel_filter =
......
......@@ -41,11 +41,24 @@ struct motion_filter;
*
* This is a superset of filter_dispatch_constant()
*
* @param filter The device's motion filter
* @param unaccelerated The unaccelerated delta in the device's dpi
* resolution as specified during filter creation. If a device has uneven
* resolution for x and y, one axis needs to be scaled to match the
* originally provided resolution.
* @param data Custom data
* @param time The time of the delta
*
* @return A set of normalized coordinates that can be used for pixel
* movement. The normalized coordiantes are scaled to the default dpi range,
* i.e. regardless of the resolution of the underlying device, the returned
* values always reflect a 1000dpi mouse.
*
* @see filter_dispatch_constant
*/
struct normalized_coords
filter_dispatch(struct motion_filter *filter,
const struct normalized_coords *unaccelerated,
const struct device_float_coords *unaccelerated,
void *data, uint64_t time);
/**
......@@ -54,11 +67,19 @@ filter_dispatch(struct motion_filter *filter,
* Takes a set of unaccelerated deltas and applies any constant filters to
* it but does not accelerate the delta in the conventional sense.
*
* @param filter The device's motion filter
* @param unaccelerated The unaccelerated delta in the device's dpi
* resolution as specified during filter creation. If a device has uneven
* resolution for x and y, one axis needs to be scaled to match the
* originally provided resolution.
* @param data Custom data
* @param time The time of the delta
*
* @see filter_dispatch
*/
struct normalized_coords
filter_dispatch_constant(struct motion_filter *filter,
const struct normalized_coords *unaccelerated,
const struct device_float_coords *unaccelerated,
void *data, uint64_t time);
void
......
......@@ -676,6 +676,12 @@ device_float_average(struct device_float_coords a, struct device_float_coords b)
return average;
}
static inline bool
device_float_is_zero(struct device_float_coords coords)
{
return coords.x == 0.0 && coords.y == 0.0;
}
static inline double
normalized_length(struct normalized_coords norm)
{
......
......@@ -38,7 +38,8 @@
static void
print_ptraccel_deltas(struct motion_filter *filter, double step)
{
struct normalized_coords motion;
struct device_float_coords motion;
struct normalized_coords accel;
uint64_t time = 0;
double i;
......@@ -55,9 +56,9 @@ print_ptraccel_deltas(struct motion_filter *filter, double step)
motion.y = 0;
time += us(12500); /* pretend 80Hz data */
motion = filter_dispatch(filter, &motion, NULL, time);
accel = filter_dispatch(filter, &motion, NULL, time);
printf("%.2f %.3f\n", i, motion.x);
printf("%.2f %.3f\n", i, accel.x);
}
}
......@@ -67,7 +68,8 @@ print_ptraccel_movement(struct motion_filter *filter,
double max_dx,
double step)
{
struct normalized_coords motion;
struct device_float_coords motion;
struct normalized_coords accel;
uint64_t time = 0;
double dx;
int i;
......@@ -98,9 +100,9 @@ print_ptraccel_movement(struct motion_filter *filter,
motion.y = 0;
time += us(12500); /* pretend 80Hz data */
motion = filter_dispatch(filter, &motion, NULL, time);
accel = filter_dispatch(filter, &motion, NULL, time);
printf("%d %.3f %.3f\n", i, motion.x, dx);
printf("%d %.3f %.3f\n", i, accel.x, dx);
if (dx < max_dx)
dx += step;
......@@ -112,7 +114,8 @@ print_ptraccel_sequence(struct motion_filter *filter,