dix: refactor pointer acceleration
The algorithm is split in a 2D-specific and a general part. This potentially allows to accelerate more than just screen motion. A state machine is intoduced to make code more explicit and readable. It also improves handling of 'phase 1' mickeys when axial correction kicks in (corner case). Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
This commit is contained in:
parent
ac470dfb4f
commit
ed9d58c3c2
144
dix/ptrveloc.c
144
dix/ptrveloc.c
|
@ -81,7 +81,8 @@ SimpleSmoothProfile(DeviceVelocityPtr pVel, float velocity,
|
|||
float threshold, float acc);
|
||||
static PointerAccelerationProfileFunc
|
||||
GetAccelerationProfile(DeviceVelocityPtr s, int profile_num);
|
||||
|
||||
static short
|
||||
ProcessVelocityData(DeviceVelocityPtr s, float distance, int diff);
|
||||
|
||||
/*#define PTRACCEL_DEBUGGING*/
|
||||
|
||||
|
@ -519,38 +520,35 @@ GetAxis(int dx, int dy){
|
|||
return 1;
|
||||
if(dy == 1 || dy == -1)
|
||||
return 2;
|
||||
return -1;
|
||||
}else{
|
||||
return -1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Perform velocity approximation
|
||||
* Perform velocity approximation based on 2D 'mickeys' (mouse motion delta).
|
||||
* return true if non-visible state reset is suggested
|
||||
*/
|
||||
static short
|
||||
ProcessVelocityData(
|
||||
ProcessVelocityData2D(
|
||||
DeviceVelocityPtr s,
|
||||
int dx,
|
||||
int dy,
|
||||
int time)
|
||||
{
|
||||
float cvelocity;
|
||||
float distance;
|
||||
|
||||
int diff = time - s->lrm_time;
|
||||
int cur_ax, last_ax;
|
||||
short reset = (diff >= s->reset_time);
|
||||
BOOL reset;
|
||||
|
||||
/* remember last round's result */
|
||||
s->last_velocity = s->velocity;
|
||||
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 && !reset){
|
||||
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 mickeys */
|
||||
reported as alternating axis-aligned mickeys */
|
||||
dx += s->last_dx;
|
||||
dy += s->last_dy;
|
||||
diff += s->last_diff;
|
||||
|
@ -560,6 +558,33 @@ ProcessVelocityData(
|
|||
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;
|
||||
|
||||
/*
|
||||
* cvelocity is not a real velocity yet, more a motion delta. constant
|
||||
* acceleration is multiplied here to make the velocity an on-screen
|
||||
|
@ -567,9 +592,7 @@ ProcessVelocityData(
|
|||
* make multiple devices with widely varying ConstantDecelerations respond
|
||||
* similar to acceleration controls.
|
||||
*/
|
||||
cvelocity = (float)sqrt(dx*dx + dy*dy) * s->const_acceleration;
|
||||
|
||||
s->lrm_time = time;
|
||||
cvelocity = distance * s->const_acceleration;
|
||||
|
||||
if (s->reset_time < 0 || diff < 0) { /* reset disabled or timer overrun? */
|
||||
/* simply set velocity from current movement, no reset. */
|
||||
|
@ -577,6 +600,24 @@ ProcessVelocityData(
|
|||
return FALSE;
|
||||
}
|
||||
|
||||
/* try to determine 'phase', i.e. whether we process the first(0),
|
||||
* second(1) or any following motion event of a stroke(2) */
|
||||
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*/
|
||||
|
||||
|
@ -584,47 +625,47 @@ ProcessVelocityData(
|
|||
so we multiply by some per-device adjustable factor) */
|
||||
cvelocity = cvelocity * s->corr_mul / (float)diff;
|
||||
|
||||
/* short-circuit: when nv-reset the rest can be skipped */
|
||||
if(reset == TRUE){
|
||||
switch(phase){
|
||||
case 0:
|
||||
/*
|
||||
* we don't really have a velocity here, since diff includes inactive
|
||||
* time. This is dealt with in ComputeAcceleration.
|
||||
* 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;
|
||||
s->last_reset = TRUE;
|
||||
DebugAccelF("(dix ptracc) non-visible state reset\n");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if(s->last_reset == 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.
|
||||
*/
|
||||
s->last_reset = FALSE;
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -696,7 +737,7 @@ ComputeAcceleration(
|
|||
float acc){
|
||||
float res;
|
||||
|
||||
if(vel->last_reset){
|
||||
if(vel->last_phase == 0){
|
||||
DebugAccelF("(dix ptracc) profile skipped\n");
|
||||
/*
|
||||
* This is intended to override the first estimate of a stroke,
|
||||
|
@ -1009,6 +1050,7 @@ acceleratePointerPredictable(
|
|||
DeviceVelocityPtr velocitydata =
|
||||
(DeviceVelocityPtr) pDev->valuator->accelScheme.accelData;
|
||||
float fdx, fdy; /* no need to init */
|
||||
Bool soften = TRUE;
|
||||
|
||||
if (!num_valuators || !valuators || !velocitydata)
|
||||
return;
|
||||
|
@ -1023,16 +1065,15 @@ acceleratePointerPredictable(
|
|||
}
|
||||
|
||||
if (dx || dy){
|
||||
/* reset nonvisible state? */
|
||||
if (ProcessVelocityData(velocitydata, dx , dy, evtime)) {
|
||||
/* set to center of pixel. makes sense as long as there are no
|
||||
* means of passing on sub-pixel values.
|
||||
/* reset non-visible state? */
|
||||
if (ProcessVelocityData2D(velocitydata, dx , dy, evtime)) {
|
||||
/* if nv-reset: set to center of pixel.
|
||||
* makes sense as long as there are no means of passing on
|
||||
* sub-pixel values to apps(XI2?). If you remove it, make
|
||||
* sure suitable rounding is applied below.
|
||||
*/
|
||||
pDev->last.remainder[0] = pDev->last.remainder[1] = 0.5f;
|
||||
/* prevent softening (somewhat quirky solution,
|
||||
as it depends on the algorithm) */
|
||||
velocitydata->last_dx = dx;
|
||||
velocitydata->last_dy = dy;
|
||||
soften = FALSE;
|
||||
}
|
||||
|
||||
if (pDev->ptrfeed && pDev->ptrfeed->ctrl.num) {
|
||||
|
@ -1044,9 +1085,10 @@ acceleratePointerPredictable(
|
|||
|
||||
if(mult != 1.0 || velocitydata->const_acceleration != 1.0) {
|
||||
ApplySofteningAndConstantDeceleration( velocitydata,
|
||||
dx, dy,
|
||||
&fdx, &fdy,
|
||||
mult > 1.0);
|
||||
dx, dy,
|
||||
&fdx, &fdy,
|
||||
(mult > 1.0) && soften);
|
||||
|
||||
if (dx) {
|
||||
pDev->last.remainder[0] = mult * fdx + pDev->last.remainder[0];
|
||||
*px = (int)pDev->last.remainder[0];
|
||||
|
|
|
@ -83,7 +83,7 @@ typedef struct _DeviceVelocityRec {
|
|||
int lrm_time; /* time the last motion event was processed */
|
||||
int last_dx, last_dy; /* last motion delta */
|
||||
int last_diff; /* last time-difference */
|
||||
Bool last_reset; /* whether a nv-reset occurred just before */
|
||||
int last_phase; /* phase of last/current estimate */
|
||||
float corr_mul; /* config: multiply this into velocity */
|
||||
float const_acceleration; /* config: (recipr.) const deceleration */
|
||||
float min_acceleration; /* config: minimum acceleration */
|
||||
|
|
Loading…
Reference in New Issue