dix/xfree86: simplified velocity approximation algorithm

Replace multi-stage filtering with simple linear velocity,
tracked several instances backwards. A heuristic ensures
only approximately linear motion is considered, so velocity
remains valid in any case. Numerical stability is much
better, and nothing changes to people who didn't tune the
advanced features of the previous algorithm.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
This commit is contained in:
Simon Thum 2009-02-28 22:17:47 +01:00 committed by Peter Hutterer
parent 5ae129baef
commit 1a71862d33
3 changed files with 236 additions and 367 deletions

View File

@ -1,6 +1,6 @@
/* /*
* *
* Copyright © 2006-2008 Simon Thum simon dot thum at gmx dot de * Copyright © 2006-2009 Simon Thum simon dot thum at gmx dot de
* *
* Permission is hereby granted, free of charge, to any person obtaining a * Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"), * copy of this software and associated documentation files (the "Software"),
@ -39,7 +39,7 @@
/***************************************************************************** /*****************************************************************************
* Predictable pointer acceleration * Predictable pointer acceleration
* *
* 2006-2008 by Simon Thum (simon [dot] thum [at] gmx de) * 2006-2009 by Simon Thum (simon [dot] thum [at] gmx de)
* *
* Serves 3 complementary functions: * Serves 3 complementary functions:
* 1) provide a sophisticated ballistic velocity estimate to improve * 1) provide a sophisticated ballistic velocity estimate to improve
@ -63,26 +63,13 @@
****************************************************************************/ ****************************************************************************/
/* fwds */ /* fwds */
static inline void
FeedFilterStage(FilterStagePtr s, float value, int tdiff);
extern void
InitFilterStage(FilterStagePtr s, float rdecay, int lutsize);
void
CleanupFilterChain(DeviceVelocityPtr s);
int int
SetAccelerationProfile(DeviceVelocityPtr s, int profile_num); SetAccelerationProfile(DeviceVelocityPtr s, int profile_num);
void
InitFilterChain(DeviceVelocityPtr s, float rdecay, float degression,
int stages, int lutsize);
void
CleanupFilterChain(DeviceVelocityPtr s);
static float static float
SimpleSmoothProfile(DeviceVelocityPtr pVel, float velocity, SimpleSmoothProfile(DeviceVelocityPtr pVel, float velocity,
float threshold, float acc); float threshold, float acc);
static PointerAccelerationProfileFunc static PointerAccelerationProfileFunc
GetAccelerationProfile(DeviceVelocityPtr s, int profile_num); GetAccelerationProfile(DeviceVelocityPtr s, int profile_num);
static short
ProcessVelocityData(DeviceVelocityPtr s, float distance, int diff);
/*#define PTRACCEL_DEBUGGING*/ /*#define PTRACCEL_DEBUGGING*/
@ -109,10 +96,12 @@ InitVelocityData(DeviceVelocityPtr s)
s->reset_time = 300; s->reset_time = 300;
s->use_softening = 1; s->use_softening = 1;
s->min_acceleration = 1.0; /* don't decelerate */ s->min_acceleration = 1.0; /* don't decelerate */
s->coupling = 0.25; s->max_rel_diff = 0.2;
s->max_diff = 1.0;
s->initial_range = 1;
s->average_accel = TRUE; s->average_accel = TRUE;
SetAccelerationProfile(s, AccelProfileClassic); SetAccelerationProfile(s, AccelProfileClassic);
InitFilterChain(s, (float)1.0/20.0, 1, 1, 40); InitTrackers(s, 16);
} }
@ -121,7 +110,7 @@ InitVelocityData(DeviceVelocityPtr s)
*/ */
static void static void
FreeVelocityData(DeviceVelocityPtr s){ FreeVelocityData(DeviceVelocityPtr s){
CleanupFilterChain(s); xfree(s->tracker);
SetAccelerationProfile(s, -1); SetAccelerationProfile(s, -1);
} }
@ -346,184 +335,219 @@ InitializePredictableAccelerationProperties(DeviceIntPtr device)
} }
/********************* /*********************
* Filtering logic * Tracking logic
********************/ ********************/
/**
Initialize a filter chain.
Expected result is a series of filters, each progressively more integrating.
This allows for two strategies: Either you have one filter which is reasonable
and is being coupled to account for fast-changing input, or you have 'one for
every situation'. You might want to have tighter coupling then, e.g. 0.1.
In the filter stats, you can see if a reasonable filter useage emerges.
*/
void void
InitFilterChain(DeviceVelocityPtr s, float rdecay, float progression, int stages, int lutsize) InitTrackers(DeviceVelocityPtr s, int ntracker)
{ {
int fn; if(ntracker < 1){
if((stages > 1 && progression < 1.0f) || 0 == progression){ ErrorF("(dix ptracc) invalid number of trackers\n");
ErrorF("(dix ptracc) invalid filter chain progression specified\n");
return; return;
} }
/* Block here to support runtime filter adjustment */ xfree(s->tracker);
OsBlockSignals(); s->tracker = (MotionTrackerPtr)xalloc(ntracker * sizeof(MotionTracker));
for(fn = 0; fn < MAX_VELOCITY_FILTERS; fn++){ memset(s->tracker, 0, ntracker * sizeof(MotionTracker));
if(fn < stages){ s->num_tracker = ntracker;
InitFilterStage(&s->filters[fn], rdecay, lutsize);
}else{
InitFilterStage(&s->filters[fn], 0, 0);
}
rdecay /= progression;
}
/* release again. Should the input loop be threaded, we also need
* memory release here (in principle).
*/
OsReleaseSignals();
} }
void
CleanupFilterChain(DeviceVelocityPtr s)
{
int fn;
for(fn = 0; fn < MAX_VELOCITY_FILTERS; fn++)
InitFilterStage(&s->filters[fn], 0, 0);
}
static inline void
StuffFilterChain(DeviceVelocityPtr s, float value)
{
int fn;
for(fn = 0; fn < MAX_VELOCITY_FILTERS; fn++){
if(s->filters[fn].rdecay != 0)
s->filters[fn].current = value;
else break;
}
}
/** /**
* Adjust weighting decay and lut for a stage * return a bit field of possible directions.
* The weight fn is designed so its integral 0->inf is unity, so we end * 0 = N, 2 = E, 4 = S, 6 = W, in-between is as you guess.
* up with a stable (basically IIR) filter. It always draws * There's no reason against widening to more precise directions (<45 degrees),
* towards its more current input values, which have more weight the older * should it not perform well. All this is needed for is sort out non-linear
* the last input value is. * motion, so precision isn't paramount. However, one should not flag direction
* too narrow, since it would then cut the linear segment to zero size way too
* often.
*/ */
void static int
InitFilterStage(FilterStagePtr s, float rdecay, int lutsize) DoGetDirection(int dx, int dy){
{ float r;
int x; int i1, i2;
float *newlut; /* on insignificant mickeys, flag 135 degrees */
float *oldlut; if(abs(dx) < 2 && abs(dy < 2)){
/* first check diagonal cases */
if(dx > 0 && dy > 0)
return 4+8+16;
if(dx > 0 && dy < 0)
return 1+2+4;
if(dx < 0 && dy < 0)
return 1+128+64;
if(dx < 0 && dy > 0)
return 16+32+64;
/* check axis-aligned directions */
if(dx > 0)
return 2+4+8; /*E*/
if(dx < 0)
return 128+64+32; /*W*/
if(dy > 0)
return 32+16+8; /*S*/
if(dy < 0)
return 128+1+2; /*N*/
return 255; /* shouldn't happen */
}
/* else, compute angle and set appropriate flags */
#ifdef _ISOC99_SOURCE
r = atan2f(dy, dx);
#else
r = atan2(dy, dx);
#endif
/* find direction. We avoid r to become negative,
* since C has no well-defined modulo for such cases. */
r = (r+(M_PI*2.5))/(M_PI/4);
/* this intends to flag 2 directions (90 degrees),
* except on very well-aligned mickeys. */
i1 = (int)(r+0.1) % 8;
i2 = (int)(r+0.9) % 8;
if(i1 < 0 || i1 > 7 || i2 < 0 || i2 > 7)
return 255; /* shouldn't happen */
return 1 << i1 | 1 << i2;
}
s->fading_lut_size = 0; /* prevent access */ #define DIRECTION_CACHE_RANGE 5
#define DIRECTION_CACHE_SIZE (DIRECTION_CACHE_RANGE*2+1)
if(lutsize > 0){ /* cache DoGetDirection(). */
newlut = xalloc (sizeof(float)* lutsize); static int
if(!newlut) GetDirection(int dx, int dy){
return; static int cache[DIRECTION_CACHE_SIZE][DIRECTION_CACHE_SIZE];
for(x = 0; x < lutsize; x++) int i;
newlut[x] = pow(0.5, ((float)x) * rdecay); if (abs(dx) <= DIRECTION_CACHE_RANGE &&
abs(dy) <= DIRECTION_CACHE_RANGE) {
/* cacheable */
i = cache[DIRECTION_CACHE_RANGE+dx][DIRECTION_CACHE_RANGE+dy];
if(i != 0){
return i;
}else{ }else{
newlut = NULL; i = DoGetDirection(dx, dy);
cache[DIRECTION_CACHE_RANGE+dx][DIRECTION_CACHE_RANGE+dy] = i;
return i;
}
}else{
/* non-cacheable */
return DoGetDirection(dx, dy);
} }
oldlut = s->fading_lut;
s->fading_lut = newlut;
s->rdecay = rdecay;
s->fading_lut_size = lutsize;
s->current = 0;
if(oldlut != NULL)
xfree(oldlut);
} }
#undef DIRECTION_CACHE_RANGE
#undef DIRECTION_CACHE_SIZE
/* convert offset (age) to array index */
#define TRACKER_INDEX(s, d) (((s)->num_tracker + (s)->cur_tracker - (d)) % (s)->num_tracker)
static inline void static inline void
FeedFilterChain(DeviceVelocityPtr s, float value, int tdiff) FeedTrackers(DeviceVelocityPtr s, int dx, int dy, int cur_t)
{ {
int fn; int n;
for(n = 0; n < s->num_tracker; n++){
for(fn = 0; fn < MAX_VELOCITY_FILTERS; fn++){ s->tracker[n].dx += dx;
if(s->filters[fn].rdecay != 0) s->tracker[n].dy += dy;
FeedFilterStage(&s->filters[fn], value, tdiff);
else break;
} }
n = (s->cur_tracker + 1) % s->num_tracker;
s->tracker[n].dx = dx;
s->tracker[n].dy = dy;
s->tracker[n].time = cur_t;
s->tracker[n].dir = GetDirection(dx, dy);
DebugAccelF("(dix prtacc) motion [dx: %i dy: %i dir:%i diff: %i]\n",
dx, dy, s->tracker[n].dir,
cur_t - s->tracker[s->cur_tracker].time);
s->cur_tracker = n;
} }
/**
static inline void * calc velocity for given tracker, with
FeedFilterStage(FilterStagePtr s, float value, int tdiff){ * velocity scaling.
float fade; * This assumes linear motion.
if(tdiff < s->fading_lut_size) */
fade = s->fading_lut[tdiff]; static float
CalcTracker(DeviceVelocityPtr s, int offset, int cur_t){
int index = TRACKER_INDEX(s, offset);
float dist = sqrt( s->tracker[index].dx * s->tracker[index].dx
+ s->tracker[index].dy * s->tracker[index].dy);
int dtime = cur_t - s->tracker[TRACKER_INDEX(s, offset+1)].time;
if(dtime > 0)
return (dist / dtime);
else else
fade = pow(0.5, ((float)tdiff) * s->rdecay); return 0;/* synonymous for NaN, since we're not C99 */
s->current *= fade; /* fade out old velocity */
s->current += value * (1.0f - fade); /* and add up current */
} }
/** /* find the most plausible velocity. That is, the most distant
* Select the most filtered matching result. Also, the first * (in time) tracker which isn't too old, beyond a linear partition,
* mismatching filter may be set to value (coupling). * or simply too much off initial velocity.
*
* min_t should be (now - ~100-600 ms). May return 0.
*/ */
static inline float static float
QueryFilterChain( QueryTrackers(DeviceVelocityPtr s, int min_t, int cur_t){
DeviceVelocityPtr s, int n, offset, dir = 255, i = -1;
float value) /* initial velocity: a low-offset, valid velocity */
{ float iveloc = 0, res = 0, tmp, vdiff;
int fn, rfn = 0, cfn = -1; float vfac = s->corr_mul * s->const_acceleration; /* premultiply */
float cur, result = value; /* loop from current to older data */
for(offset = 0; offset < s->num_tracker-1; offset++){
n = TRACKER_INDEX(s, offset);
/* try to retrieve most integrated result 'within range' /* bail out if data is too old */
* Assumption: filter are in order least to most integrating */ if(s->tracker[TRACKER_INDEX(s, offset+1)].time < min_t){
for(fn = 0; fn < MAX_VELOCITY_FILTERS; fn++){ DebugAccelF("(dix prtacc) query: tracker too old\n");
if(0.0f == s->filters[fn].rdecay)
break; break;
cur = s->filters[fn].current;
if (fabs(value - cur) <= (s->coupling * (value + cur))){
result = cur;
rfn = fn + 1; /*remember result determining filter */
} else if(cfn == -1){
cfn = fn; /* remember first mismatching filter */
}
} }
s->statistics.filter_usecount[rfn]++; /*
DebugAccelF("(dix ptracc) result from stage %i, input %.2f, output %.2f\n", * this heuristic avoids using the linear-motion velocity formula
rfn, value, result); * in CalcTracker() on motion that isn't exactly linear. So to get
* even more precision we could subdivide as a final step, so possible
/* override first mismatching current (coupling) so the filter * non-linearities are accounted for.
* catches up quickly. */
if(cfn != -1)
s->filters[cfn].current = result;
return result;
}
/********************************
* velocity computation
*******************************/
/**
* return the axis if mickey is insignificant and axis-aligned,
* -1 otherwise
* 1 for x-axis
* 2 for y-axis
*/ */
static inline short dir &= s->tracker[n].dir;
GetAxis(int dx, int dy){ if(dir == 0){
if(dx == 0 || dy == 0){ DebugAccelF("(dix prtacc) query: no longer linear\n");
if(dx == 1 || dx == -1) /* instead of breaking it we might also inspect the partition after,
return 1; * but actual improvement with this is probably rare. */
if(dy == 1 || dy == -1) break;
return 2;
} }
return -1;
tmp = CalcTracker(s, offset, cur_t) * vfac;
if ((iveloc == 0 || offset <= s->initial_range) && tmp != 0) {
/* set initial velocity and result */
res = iveloc = tmp;
i = offset;
} else if (iveloc != 0 && tmp != 0) {
vdiff = fabs(iveloc - tmp);
if (vdiff <= s->max_diff ||
vdiff/(iveloc + tmp) < s->max_rel_diff) {
/* we're in range with the initial velocity,
* so this result is likely better
* (it contains more information). */
res = tmp;
i = offset;
}else{
/* we're not in range, quit - it won't get better. */
DebugAccelF("(dix prtacc) query: tracker too different:"
" old %2.2f initial %2.2f diff: %2.2f\n",
tmp, iveloc, vdiff);
break;
}
}
}
if(offset == s->num_tracker){
DebugAccelF("(dix prtacc) query: last tracker in effect\n");
i = s->num_tracker-1;
}
if(i>=0){
n = TRACKER_INDEX(s, i);
DebugAccelF("(dix prtacc) result: offset %i [dx: %i dy: %i diff: %i]\n",
i,
s->tracker[n].dx,
s->tracker[n].dy,
cur_t - s->tracker[n].time);
}
return res;
} }
#undef TRACKER_INDEX
/** /**
* Perform velocity approximation based on 2D 'mickeys' (mouse motion delta). * Perform velocity approximation based on 2D 'mickeys' (mouse motion delta).
@ -536,139 +560,18 @@ ProcessVelocityData2D(
int dy, int dy,
int time) int time)
{ {
float distance; float velocity;
int diff = time - s->lrm_time;
int cur_ax, last_ax;
BOOL reset;
cur_ax = GetAxis(dx, dy);
last_ax = GetAxis(s->last_dx, s->last_dy);
if(cur_ax != last_ax && cur_ax != -1 && last_ax != -1 &&
diff < s->reset_time){
/* correct for the error induced when diagonal movements are
reported as alternating axis-aligned mickeys */
dx += s->last_dx;
dy += s->last_dy;
diff += s->last_diff;
s->last_diff = time - s->lrm_time; /* prevent repeating add-up */
DebugAccelF("(dix ptracc) axial correction\n");
}else{
s->last_diff = diff;
}
distance = (float)sqrt(dx*dx + dy*dy);
s->lrm_time = time;
reset = ProcessVelocityData(s, distance, diff);
if(reset)
s->last_diff = 1;
return reset;
}
/**
* Perform velocity approximation given a one-dimensional delta.
* return true if non-visible state reset is suggested
*/
static short
ProcessVelocityData(
DeviceVelocityPtr s,
float distance,
int diff)
{
float cvelocity;
int phase;
/* remember previous result */
s->last_velocity = s->velocity; s->last_velocity = s->velocity;
/* FeedTrackers(s, dx, dy, time);
* cvelocity is not a real velocity yet, more a motion delta. constant
* acceleration is multiplied here to make the velocity an on-screen
* velocity (pix/t as opposed to [insert unit]/t). This is intended to
* make multiple devices with widely varying ConstantDecelerations respond
* similar to acceleration controls.
*/
cvelocity = distance * s->const_acceleration;
if (s->reset_time < 0 || diff < 0) { /* reset disabled or timer overrun? */ velocity = QueryTrackers(s, time - s->reset_time, time);
/* simply set velocity from current movement, no reset. */
s->velocity = cvelocity;
return FALSE;
}
/* try to determine 'phase', i.e. whether we process the first(0), s->velocity = velocity;
* second(1) or any following motion event of a stroke(2) */ return velocity == 0;
if(diff >= s->reset_time && cvelocity < 10){
phase = 0;
}else{
switch(s->last_phase){
case 0:
case 1:
phase = s->last_phase + 1;
break;
default:
phase = 2;
break;
}
}
s->last_phase = phase;
DebugAccelF("(dix ptracc) phase: %i\n", phase);
if (diff == 0)
diff = 1; /* prevent div-by-zero, though it shouldn't happen anyway*/
/* translate velocity to dots/ms (somewhat intractable in integers,
so we multiply by some per-device adjustable factor) */
cvelocity = cvelocity * s->corr_mul / (float)diff;
switch(phase){
case 0:
/*
* First event of a stroke.
* We don't really have a velocity here, since diff includes inactive
* time. This is dealt with in ComputeAcceleration. Here we cancel out
* remnants from previous strokes which the user is presumably
* not aware of (non-visible state reset).
*/
StuffFilterChain(s, cvelocity);
s->velocity = s->last_velocity = cvelocity;
DebugAccelF("(dix ptracc) non-visible state reset\n");
return TRUE;
case 1:
/*
* when here, we're probably processing the second mickey of a starting
* stroke. This happens to be the first time we can reasonably pretend
* that cvelocity is an actual velocity. Thus, to opt precision, we
* stuff that into the filter chain.
*/
DebugAccelF("(dix ptracc) after-reset vel:%.3f\n", cvelocity);
StuffFilterChain(s, cvelocity);
s->velocity = cvelocity;
return FALSE;
default:
/* normal operarion: feed into filter chain */
FeedFilterChain(s, cvelocity, diff);
/* perform coupling and decide final value */
s->velocity = QueryFilterChain(s, cvelocity);
DebugAccelF(
"(dix ptracc) guess: vel=%.3f diff=%d %i|%i|%i|%i|%i|%i|%i|%i|%i\n",
s->velocity, diff,
s->statistics.filter_usecount[0], s->statistics.filter_usecount[1],
s->statistics.filter_usecount[2], s->statistics.filter_usecount[3],
s->statistics.filter_usecount[4], s->statistics.filter_usecount[5],
s->statistics.filter_usecount[6], s->statistics.filter_usecount[7],
s->statistics.filter_usecount[8]);
return FALSE;
}
} }
/** /**
* this flattens significant ( > 1) mickeys a little bit for more steady * this flattens significant ( > 1) mickeys a little bit for more steady
* constant-velocity response * constant-velocity response
@ -737,12 +640,10 @@ ComputeAcceleration(
float acc){ float acc){
float res; float res;
if(vel->last_phase == 0){ if(vel->velocity <= 0){
DebugAccelF("(dix ptracc) profile skipped\n"); DebugAccelF("(dix ptracc) profile skipped\n");
/* /*
* This is intended to override the first estimate of a stroke, * If we have no idea about device velocity, don't pretend it.
* which is too low (see ProcessVelocityData). 1 should make sure
* the mickey is seen on screen.
*/ */
return 1; return 1;
} }
@ -1072,7 +973,7 @@ acceleratePointerPredictable(
* sub-pixel values to apps(XI2?). If you remove it, make * sub-pixel values to apps(XI2?). If you remove it, make
* sure suitable rounding is applied below. * sure suitable rounding is applied below.
*/ */
pDev->last.remainder[0] = pDev->last.remainder[1] = 0.5f; pDev->last.remainder[0] = pDev->last.remainder[1] = 0;
soften = FALSE; soften = FALSE;
} }
@ -1090,12 +991,12 @@ acceleratePointerPredictable(
(mult > 1.0) && soften); (mult > 1.0) && soften);
if (dx) { if (dx) {
pDev->last.remainder[0] = mult * fdx + pDev->last.remainder[0]; pDev->last.remainder[0] = roundf(mult * fdx + pDev->last.remainder[0]);
*px = (int)pDev->last.remainder[0]; *px = (int)pDev->last.remainder[0];
pDev->last.remainder[0] = pDev->last.remainder[0] - (float)*px; pDev->last.remainder[0] = pDev->last.remainder[0] - (float)*px;
} }
if (dy) { if (dy) {
pDev->last.remainder[1] = mult * fdy + pDev->last.remainder[1]; pDev->last.remainder[1] = roundf(mult * fdy + pDev->last.remainder[1]);
*py = (int)pDev->last.remainder[1]; *py = (int)pDev->last.remainder[1];
pDev->last.remainder[1] = pDev->last.remainder[1] - (float)*py; pDev->last.remainder[1] = pDev->last.remainder[1] - (float)*py;
} }

View File

@ -104,8 +104,8 @@ static void
ProcessVelocityConfiguration(DeviceIntPtr pDev, char* devname, pointer list, ProcessVelocityConfiguration(DeviceIntPtr pDev, char* devname, pointer list,
DeviceVelocityPtr s) DeviceVelocityPtr s)
{ {
int tempi, i; int tempi;
float tempf, tempf2; float tempf;
Atom float_prop = XIGetKnownProperty(XATOM_FLOAT); Atom float_prop = XIGetKnownProperty(XATOM_FLOAT);
Atom prop; Atom prop;
@ -150,10 +150,6 @@ ProcessVelocityConfiguration(DeviceIntPtr pDev, char* devname, pointer list,
tempf = xf86SetRealOption(list, "ExpectedRate", 0); tempf = xf86SetRealOption(list, "ExpectedRate", 0);
prop = XIGetKnownProperty(ACCEL_PROP_VELOCITY_SCALING); prop = XIGetKnownProperty(ACCEL_PROP_VELOCITY_SCALING);
if(tempf > 0){ if(tempf > 0){
if(tempf > 300){
xf86Msg(X_WARNING, "%s: (accel) Using ExpectedRate > 300 may not "
"yield good controllability!\n", devname);
}
tempf = 1000.0 / tempf; tempf = 1000.0 / tempf;
XIChangeDeviceProperty(pDev, prop, float_prop, 32, XIChangeDeviceProperty(pDev, prop, float_prop, 32,
PropModeReplace, 1, &tempf, FALSE); PropModeReplace, 1, &tempf, FALSE);
@ -163,38 +159,21 @@ ProcessVelocityConfiguration(DeviceIntPtr pDev, char* devname, pointer list,
PropModeReplace, 1, &tempf, FALSE); PropModeReplace, 1, &tempf, FALSE);
} }
/* advanced stuff, best kept in .fdi's */ tempi = xf86SetIntOption(list, "VelocityTrackerCount", -1);
tempf = xf86SetRealOption(list, "FilterHalflife", -1); if(tempi > 1){
if(tempf > 0) InitTrackers(s, tempi);
tempf = 1.0 / tempf; /* set reciprocal if possible */
tempf2 = xf86SetRealOption(list, "FilterChainProgression", 2.0);
xf86Msg(X_CONFIG, "%s: (accel) filter chain progression: %.2f\n",
devname, tempf2);
if(tempf2 < 1)
tempf2 = 2;
tempi = xf86SetIntOption(list, "FilterChainLength", 1);
if(tempi < 1 || tempi > MAX_VELOCITY_FILTERS)
tempi = 1;
if(tempf > 0.0f && tempi >= 1 && tempf2 >= 1.0f)
InitFilterChain(s, tempf, tempf2, tempi, 40);
/* dump filter setup to log */
for(i = 0; i < MAX_VELOCITY_FILTERS; i++){
if(s->filters[i].rdecay <= 0)
break;
xf86Msg(X_CONFIG, "%s: (accel) filter stage %i: %.2f ms\n",
devname, i, 1.0f / (s->filters[i].rdecay));
} }
tempf = xf86SetRealOption(list, "VelocityCoupling", -1); s->initial_range = xf86SetIntOption(list, "VelocityInitialRange",
s->initial_range);
s->max_diff = xf86SetRealOption(list, "VelocityAbsDiff", s->max_diff);
tempf = xf86SetRealOption(list, "VelocityRelDiff", -1);
if(tempf >= 0){ if(tempf >= 0){
xf86Msg(X_CONFIG, "%s: (accel) velocity coupling is %.1f%%\n", devname, xf86Msg(X_CONFIG, "%s: (accel) max rel. velocity difference: %.1f%%\n",
tempf*100.0); devname, tempf*100.0);
s->coupling = tempf; s->max_rel_diff = tempf;
} }
/* Configure softening. If const deceleration is used, this is expected /* Configure softening. If const deceleration is used, this is expected

View File

@ -27,11 +27,6 @@
#include <input.h> /* DeviceIntPtr */ #include <input.h> /* DeviceIntPtr */
/* maximum number of filters to approximate velocity.
* ABI-breaker!
*/
#define MAX_VELOCITY_FILTERS 8
/* constants for acceleration profiles; /* constants for acceleration profiles;
* see */ * see */
@ -57,46 +52,41 @@ typedef float (*PointerAccelerationProfileFunc)
float /*velocity*/, float /*threshold*/, float /*acc*/); float /*velocity*/, float /*threshold*/, float /*acc*/);
/** /**
* a filter stage contains the data for adaptive IIR filtering. * a motion history, with just enough information to
* To improve results, one may run several parallel filters * calc mean velocity and decide which motion was along
* which have different decays. Since more integration means more * a more or less straight line
* delay, a given filter only does good matches in a specific phase of
* a stroke.
*
* Basically, the coupling feature makes one filter fairly enough,
* so that is the default.
*/ */
typedef struct _FilterStage { typedef struct _MotionTracker {
float* fading_lut; /* lookup for adaptive IIR filter */ int dx, dy; /* accumulated delta for each axis */
int fading_lut_size; /* size of lookup table */ int time; /* time of creation */
float rdecay; /* reciprocal weighting halflife in ms */ int dir; /* initial direction bitfield */
float current; } MotionTracker, *MotionTrackerPtr;
} FilterStage, *FilterStagePtr;
/** /**
* Contains all data needed to implement mouse ballistics * Contains all data needed to implement mouse ballistics
*/ */
typedef struct _DeviceVelocityRec { typedef struct _DeviceVelocityRec {
FilterStage filters[MAX_VELOCITY_FILTERS]; MotionTrackerPtr tracker;
int num_tracker;
int cur_tracker; /* current index */
float velocity; /* velocity as guessed by algorithm */ float velocity; /* velocity as guessed by algorithm */
float last_velocity; /* previous velocity estimate */ float last_velocity; /* previous velocity estimate */
int lrm_time; /* time the last motion event was processed */ int last_dx; /* last time-difference */
int last_dx, last_dy; /* last motion delta */ int last_dy ; /* phase of last/current estimate */
int last_diff; /* last time-difference */
int last_phase; /* phase of last/current estimate */
float corr_mul; /* config: multiply this into velocity */ float corr_mul; /* config: multiply this into velocity */
float const_acceleration; /* config: (recipr.) const deceleration */ float const_acceleration; /* config: (recipr.) const deceleration */
float min_acceleration; /* config: minimum acceleration */ float min_acceleration; /* config: minimum acceleration */
short reset_time; /* config: reset non-visible state after # ms */ short reset_time; /* config: reset non-visible state after # ms */
short use_softening; /* config: use softening of mouse values */ short use_softening; /* config: use softening of mouse values */
float coupling; /* config: max. divergence before coupling */ float max_rel_diff; /* config: max. relative difference */
float max_diff; /* config: max. difference */
int initial_range; /* config: max. offset used as initial velocity */
Bool average_accel; /* config: average acceleration over velocity */ Bool average_accel; /* config: average acceleration over velocity */
PointerAccelerationProfileFunc Profile; PointerAccelerationProfileFunc Profile;
PointerAccelerationProfileFunc deviceSpecificProfile; PointerAccelerationProfileFunc deviceSpecificProfile;
void* profile_private;/* extended data, see SetAccelerationProfile() */ void* profile_private;/* extended data, see SetAccelerationProfile() */
struct { /* to be able to query this information */ struct { /* to be able to query this information */
int profile_number; int profile_number;
int filter_usecount[MAX_VELOCITY_FILTERS +1];
} statistics; } statistics;
} DeviceVelocityRec, *DeviceVelocityPtr; } DeviceVelocityRec, *DeviceVelocityPtr;
@ -104,13 +94,12 @@ typedef struct _DeviceVelocityRec {
extern _X_EXPORT void extern _X_EXPORT void
InitVelocityData(DeviceVelocityPtr s); InitVelocityData(DeviceVelocityPtr s);
extern _X_EXPORT void
InitTrackers(DeviceVelocityPtr s, int ntracker);
extern _X_EXPORT BOOL extern _X_EXPORT BOOL
InitializePredictableAccelerationProperties(DeviceIntPtr pDev); InitializePredictableAccelerationProperties(DeviceIntPtr pDev);
extern _X_EXPORT void
InitFilterChain(DeviceVelocityPtr s, float rdecay, float degression,
int lutsize, int stages);
extern _X_EXPORT int extern _X_EXPORT int
SetAccelerationProfile(DeviceVelocityPtr s, int profile_num); SetAccelerationProfile(DeviceVelocityPtr s, int profile_num);