dix: optimize precision in device velocity estimation

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
This commit is contained in:
Simon Thum 2008-07-23 11:49:36 +02:00 committed by Peter Hutterer
parent c184b91d9a
commit 1a9f9ac50f
2 changed files with 70 additions and 28 deletions

View File

@ -90,11 +90,12 @@ InitVelocityData(DeviceVelocityPtr s)
s->corr_mul = 10.0; /* dots per 10 milisecond should be usable */ s->corr_mul = 10.0; /* dots per 10 milisecond should be usable */
s->const_acceleration = 1.0; /* no acceleration/deceleration */ s->const_acceleration = 1.0; /* no acceleration/deceleration */
s->reset_time = 300; s->reset_time = 300;
s->last_reset = FALSE;
s->last_dx = 0; s->last_dx = 0;
s->last_dy = 0; s->last_dy = 0;
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.2; s->coupling = 0.25;
s->profile_private = NULL; s->profile_private = NULL;
memset(&s->statistics, 0, sizeof(s->statistics)); memset(&s->statistics, 0, sizeof(s->statistics));
memset(&s->filters, 0, sizeof(s->filters)); memset(&s->filters, 0, sizeof(s->filters));
@ -135,6 +136,12 @@ AccelerationDefaultCleanup(DeviceIntPtr pDev){
/** /**
Initialize a filter chain. Initialize a filter chain.
Expected result is a series of filters, each progressively more integrating. 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 loose coupling then, i.e. > 1.
E.g. you could start around 1/2 of your anticipated delta t and
scale up until several motion deltas are 'averaged'.
*/ */
void void
InitFilterChain(DeviceVelocityPtr s, float rdecay, float progression, int stages, int lutsize) InitFilterChain(DeviceVelocityPtr s, float rdecay, float progression, int stages, int lutsize)
@ -164,9 +171,21 @@ CleanupFilterChain(DeviceVelocityPtr s)
InitFilterStage(&s->filters[fn], 0, 0); 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 in sync * Adjust weighting decay and lut for a stage
* The weight fn is designed so its integral 0->inf is unity, so we end * The weight fn is designed so its integral 0->inf is unity, so we end
* up with a stable (basically IIR) filter. It always draws * up with a stable (basically IIR) filter. It always draws
* towards its more current input values, which have more weight the older * towards its more current input values, which have more weight the older
@ -227,13 +246,12 @@ FeedFilterStage(FilterStagePtr s, float value, int tdiff){
/** /**
* Select the most filtered matching result. Also, the first * Select the most filtered matching result. Also, the first
* mismatching filter will be set to value (coupling). * mismatching filter may be set to value (coupling).
*/ */
static inline float static inline float
QueryFilterChain( QueryFilterChain(
DeviceVelocityPtr s, DeviceVelocityPtr s,
float value, float value)
float maxdiv)
{ {
int fn, rfn = 0, cfn = -1; int fn, rfn = 0, cfn = -1;
float cur, result = value; float cur, result = value;
@ -246,20 +264,21 @@ QueryFilterChain(
cur = s->filters[fn].current; cur = s->filters[fn].current;
if (fabs(value - cur) <= 1.0f || if (fabs(value - cur) <= 1.0f ||
fabs(value - cur) / (value + cur) <= maxdiv){ fabs(value - cur) / (value + cur) <= s->coupling){
result = cur; result = cur;
rfn = fn; /*remember result determining filter */ rfn = fn + 1; /*remember result determining filter */
} else if(cfn == -1){ } else if(cfn == -1){
cfn = fn; /* rememeber first mismatching filter */ cfn = fn; /* rememeber first mismatching filter */
} }
} }
s->statistics.filter_usecount[rfn]++; s->statistics.filter_usecount[rfn]++;
#ifdef SERIOUS_DEBUGGING #ifdef PTRACCEL_DEBUGGING
ErrorF("(dix ptraccel) result from filter stage %i, input %.2f, output %.2f\n", rfn, value, result); ErrorF("(dix ptraccel) result from stage %i, input %.2f, output %.2f\n",
rfn, value, result);
#endif #endif
/* override one current (coupling) so the filter /* override first mismatching current (coupling) so the filter
* catches up quickly. */ * catches up quickly. */
if(cfn != -1) if(cfn != -1)
s->filters[cfn].current = result; s->filters[cfn].current = result;
@ -296,7 +315,11 @@ GetAxis(int dx, int dy){
* return true if non-visible state reset is suggested * return true if non-visible state reset is suggested
*/ */
static short static short
ProcessVelocityData(DeviceVelocityPtr s, int dx, int dy, int time) ProcessVelocityData(
DeviceVelocityPtr s,
int dx,
int dy,
int time)
{ {
float cvelocity; float cvelocity;
@ -312,7 +335,7 @@ ProcessVelocityData(DeviceVelocityPtr s, int dx, int dy, int time)
dy += s->last_dy; dy += s->last_dy;
diff += s->last_diff; diff += s->last_diff;
s->last_diff = time - s->lrm_time; /* prevent repeating add-up */ s->last_diff = time - s->lrm_time; /* prevent repeating add-up */
#ifdef SERIOUS_DEBUGGING #ifdef PTRACCEL_DEBUGGING
ErrorF("(dix ptracc) axial correction\n"); ErrorF("(dix ptracc) axial correction\n");
#endif #endif
}else{ }else{
@ -320,9 +343,9 @@ ProcessVelocityData(DeviceVelocityPtr s, int dx, int dy, int time)
} }
/* /*
* cvelocity is not a real velocity yet, more a motion delta. contant * cvelocity is not a real velocity yet, more a motion delta. constant
* acceleration is multiplied here to make the velocity an on-screen * acceleration is multiplied here to make the velocity an on-screen
* velocity (px/t as opposed to [insert unit]/t). This is intended to * velocity (pix/t as opposed to [insert unit]/t). This is intended to
* make multiple devices with widely varying ConstantDecelerations respond * make multiple devices with widely varying ConstantDecelerations respond
* similar to acceleration controls. * similar to acceleration controls.
*/ */
@ -330,10 +353,10 @@ ProcessVelocityData(DeviceVelocityPtr s, int dx, int dy, int time)
s->lrm_time = time; s->lrm_time = time;
if (s->reset_time < 0 || diff < 0) { /* disabled or timer overrun? */ if (s->reset_time < 0 || diff < 0) { /* reset disabled or timer overrun? */
/* simply set velocity from current movement, no reset. */ /* simply set velocity from current movement, no reset. */
s->velocity = cvelocity; s->velocity = cvelocity;
return 0; return FALSE;
} }
if (diff == 0) if (diff == 0)
@ -345,23 +368,38 @@ ProcessVelocityData(DeviceVelocityPtr s, int dx, int dy, int time)
/* short-circuit: when nv-reset the rest can be skipped */ /* short-circuit: when nv-reset the rest can be skipped */
if(reset == TRUE){ if(reset == TRUE){
StuffFilterChain(s, cvelocity);
s->velocity = cvelocity; s->velocity = cvelocity;
s->last_reset = TRUE;
return TRUE; return TRUE;
} }
if(s->last_reset == TRUE){
/*
* 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.
*/
s->last_reset = FALSE;
StuffFilterChain(s, cvelocity);
s->velocity = cvelocity;
return FALSE;
}
/* feed into filter chain */ /* feed into filter chain */
FeedFilterChain(s, cvelocity, diff); FeedFilterChain(s, cvelocity, diff);
/* perform coupling and decide final value */ /* perform coupling and decide final value */
s->velocity = QueryFilterChain(s, cvelocity, s->coupling); s->velocity = QueryFilterChain(s, cvelocity);
#ifdef SERIOUS_DEBUGGING #ifdef PTRACCEL_DEBUGGING
ErrorF("(dix ptracc) guess: vel=%.3f diff=%d |%i|%i|%i|%i|\n", ErrorF("(dix ptracc) guess: vel=%.3f diff=%d |%i|%i|%i|%i|\n",
s->velocity, diff, s->velocity, diff,
s->statistics.filter_usecount[0], s->statistics.filter_usecount[1], s->statistics.filter_usecount[0], s->statistics.filter_usecount[1],
s->statistics.filter_usecount[2], s->statistics.filter_usecount[3]); s->statistics.filter_usecount[2], s->statistics.filter_usecount[3]);
#endif #endif
return reset; return FALSE;
} }
@ -689,15 +727,15 @@ acceleratePointerPredictable(DeviceIntPtr pDev, int first_valuator,
/* invoke acceleration profile to determine acceleration */ /* invoke acceleration profile to determine acceleration */
mult = velocitydata->Profile(velocitydata, mult = velocitydata->Profile(velocitydata,
pDev->ptrfeed->ctrl.threshold, pDev->ptrfeed->ctrl.threshold,
(float)(pDev->ptrfeed->ctrl.num) / (float)pDev->ptrfeed->ctrl.num /
(float)(pDev->ptrfeed->ctrl.den)); (float)pDev->ptrfeed->ctrl.den);
#ifdef SERIOUS_DEBUGGING #ifdef PTRACCEL_DEBUGGING
ErrorF("(dix ptracc) resulting speed multiplier : %.3f\n", mult); ErrorF("(dix ptracc) resulting speed multiplier : %.3f\n", mult);
#endif #endif
/* enforce min_acceleration */ /* enforce min_acceleration */
if (mult < velocitydata->min_acceleration) { if (mult < velocitydata->min_acceleration) {
#ifdef SERIOUS_DEBUGGING #ifdef PTRACCEL_DEBUGGING
ErrorF("(dix ptracc) enforced min multiplier : %.3f\n", ErrorF("(dix ptracc) enforced min multiplier : %.3f\n",
velocitydata->min_acceleration); velocitydata->min_acceleration);
#endif #endif

View File

@ -27,6 +27,9 @@
#include <input.h> /* DeviceIntPtr */ #include <input.h> /* DeviceIntPtr */
/* maximum number of filters to approximate velocity.
* ABI-breaker!
*/
#define MAX_VELOCITY_FILTERS 8 #define MAX_VELOCITY_FILTERS 8
/* constants for acceleration profiles; /* constants for acceleration profiles;
@ -53,7 +56,7 @@ typedef float (*PointerAccelerationProfileFunc)
float /*threshold*/, float /*acc*/); float /*threshold*/, float /*acc*/);
/** /**
* a filter stage contains the data for the adaptive IIR filtering. * a filter stage contains the data for adaptive IIR filtering.
* To improve results, one may run several parallel filters * To improve results, one may run several parallel filters
* which have different decays. Since more integration means more * which have different decays. Since more integration means more
* delay, a given filter only does good matches in a specific phase of * delay, a given filter only does good matches in a specific phase of
@ -77,7 +80,8 @@ typedef struct _DeviceVelocityRec {
float velocity; /* velocity as guessed by algorithm */ float velocity; /* velocity as guessed by algorithm */
int lrm_time; /* time the last motion event was processed */ int lrm_time; /* time the last motion event was processed */
int last_dx, last_dy; /* last motion delta */ int last_dx, last_dy; /* last motion delta */
int last_diff; /* last time-diff */ int last_diff; /* last time-difference */
Bool last_reset; /* whether a nv-reset occurred just before */
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 */
@ -89,7 +93,7 @@ typedef struct _DeviceVelocityRec {
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]; int filter_usecount[MAX_VELOCITY_FILTERS +1];
} statistics; } statistics;
} DeviceVelocityRec, *DeviceVelocityPtr; } DeviceVelocityRec, *DeviceVelocityPtr;