ptrveloc.c revision 4642e01f
14642e01fSmrg/*
24642e01fSmrg *
34642e01fSmrg * Copyright © 2006-2008 Simon Thum             simon dot thum at gmx dot de
44642e01fSmrg *
54642e01fSmrg * Permission is hereby granted, free of charge, to any person obtaining a
64642e01fSmrg * copy of this software and associated documentation files (the "Software"),
74642e01fSmrg * to deal in the Software without restriction, including without limitation
84642e01fSmrg * the rights to use, copy, modify, merge, publish, distribute, sublicense,
94642e01fSmrg * and/or sell copies of the Software, and to permit persons to whom the
104642e01fSmrg * Software is furnished to do so, subject to the following conditions:
114642e01fSmrg *
124642e01fSmrg * The above copyright notice and this permission notice (including the next
134642e01fSmrg * paragraph) shall be included in all copies or substantial portions of the
144642e01fSmrg * Software.
154642e01fSmrg *
164642e01fSmrg * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
174642e01fSmrg * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
184642e01fSmrg * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
194642e01fSmrg * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
204642e01fSmrg * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
214642e01fSmrg * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
224642e01fSmrg * DEALINGS IN THE SOFTWARE.
234642e01fSmrg */
244642e01fSmrg
254642e01fSmrg#ifdef HAVE_DIX_CONFIG_H
264642e01fSmrg#include <dix-config.h>
274642e01fSmrg#endif
284642e01fSmrg
294642e01fSmrg#include <math.h>
304642e01fSmrg#include <ptrveloc.h>
314642e01fSmrg#include <inputstr.h>
324642e01fSmrg#include <assert.h>
334642e01fSmrg#include <os.h>
344642e01fSmrg
354642e01fSmrg/*****************************************************************************
364642e01fSmrg * Predictable pointer acceleration
374642e01fSmrg *
384642e01fSmrg * 2006-2008 by Simon Thum (simon [dot] thum [at] gmx de)
394642e01fSmrg *
404642e01fSmrg * Serves 3 complementary functions:
414642e01fSmrg * 1) provide a sophisticated ballistic velocity estimate to improve
424642e01fSmrg *    the relation between velocity (of the device) and acceleration
434642e01fSmrg * 2) make arbitrary acceleration profiles possible
444642e01fSmrg * 3) decelerate by two means (constant and adaptive) if enabled
454642e01fSmrg *
464642e01fSmrg * Important concepts are the
474642e01fSmrg *
484642e01fSmrg * - Scheme
494642e01fSmrg *      which selects the basic algorithm
504642e01fSmrg *      (see devices.c/InitPointerAccelerationScheme)
514642e01fSmrg * - Profile
524642e01fSmrg *      which returns an acceleration
534642e01fSmrg *      for a given velocity
544642e01fSmrg *
554642e01fSmrg *  The profile can be selected by the user (potentially at runtime).
564642e01fSmrg *  the classic profile is intended to cleanly perform old-style
574642e01fSmrg *  function selection (threshold =/!= 0)
584642e01fSmrg *
594642e01fSmrg ****************************************************************************/
604642e01fSmrg
614642e01fSmrg/* fwds */
624642e01fSmrgstatic inline void
634642e01fSmrgFeedFilterStage(FilterStagePtr s, float value, int tdiff);
644642e01fSmrgextern void
654642e01fSmrgInitFilterStage(FilterStagePtr s, float rdecay, int lutsize);
664642e01fSmrgvoid
674642e01fSmrgCleanupFilterChain(DeviceVelocityPtr s);
684642e01fSmrgint
694642e01fSmrgSetAccelerationProfile(DeviceVelocityPtr s, int profile_num);
704642e01fSmrgvoid
714642e01fSmrgInitFilterChain(DeviceVelocityPtr s, float rdecay, float degression,
724642e01fSmrg                int stages, int lutsize);
734642e01fSmrgvoid
744642e01fSmrgCleanupFilterChain(DeviceVelocityPtr s);
754642e01fSmrgstatic float
764642e01fSmrgSimpleSmoothProfile(DeviceVelocityPtr pVel, float velocity,
774642e01fSmrg                    float threshold, float acc);
784642e01fSmrg
794642e01fSmrg
804642e01fSmrg
814642e01fSmrg/*#define PTRACCEL_DEBUGGING*/
824642e01fSmrg
834642e01fSmrg#ifdef PTRACCEL_DEBUGGING
844642e01fSmrg#define DebugAccelF ErrorF
854642e01fSmrg#else
864642e01fSmrg#define DebugAccelF(...) /* */
874642e01fSmrg#endif
884642e01fSmrg
894642e01fSmrg/********************************
904642e01fSmrg *  Init/Uninit etc
914642e01fSmrg *******************************/
924642e01fSmrg
934642e01fSmrg/**
944642e01fSmrg * Init struct so it should match the average case
954642e01fSmrg */
964642e01fSmrgvoid
974642e01fSmrgInitVelocityData(DeviceVelocityPtr s)
984642e01fSmrg{
994642e01fSmrg    memset(s, 0, sizeof(DeviceVelocityRec));
1004642e01fSmrg
1014642e01fSmrg    s->corr_mul = 10.0;      /* dots per 10 milisecond should be usable */
1024642e01fSmrg    s->const_acceleration = 1.0;   /* no acceleration/deceleration  */
1034642e01fSmrg    s->reset_time = 300;
1044642e01fSmrg    s->use_softening = 1;
1054642e01fSmrg    s->min_acceleration = 1.0; /* don't decelerate */
1064642e01fSmrg    s->coupling = 0.25;
1074642e01fSmrg    s->average_accel = TRUE;
1084642e01fSmrg    SetAccelerationProfile(s, AccelProfileClassic);
1094642e01fSmrg    InitFilterChain(s, (float)1.0/20.0, 1, 1, 40);
1104642e01fSmrg}
1114642e01fSmrg
1124642e01fSmrg
1134642e01fSmrg/**
1144642e01fSmrg * Clean up
1154642e01fSmrg */
1164642e01fSmrgstatic void
1174642e01fSmrgFreeVelocityData(DeviceVelocityPtr s){
1184642e01fSmrg    CleanupFilterChain(s);
1194642e01fSmrg    SetAccelerationProfile(s, -1);
1204642e01fSmrg}
1214642e01fSmrg
1224642e01fSmrg
1234642e01fSmrg/*
1244642e01fSmrg *  dix uninit helper, called through scheme
1254642e01fSmrg */
1264642e01fSmrgvoid
1274642e01fSmrgAccelerationDefaultCleanup(DeviceIntPtr pDev)
1284642e01fSmrg{
1294642e01fSmrg    /*sanity check*/
1304642e01fSmrg    if( pDev->valuator->accelScheme.AccelSchemeProc == acceleratePointerPredictable
1314642e01fSmrg            && pDev->valuator->accelScheme.accelData != NULL){
1324642e01fSmrg        pDev->valuator->accelScheme.AccelSchemeProc = NULL;
1334642e01fSmrg        FreeVelocityData(pDev->valuator->accelScheme.accelData);
1344642e01fSmrg        xfree(pDev->valuator->accelScheme.accelData);
1354642e01fSmrg        pDev->valuator->accelScheme.accelData = NULL;
1364642e01fSmrg    }
1374642e01fSmrg}
1384642e01fSmrg
1394642e01fSmrg/*********************
1404642e01fSmrg * Filtering logic
1414642e01fSmrg ********************/
1424642e01fSmrg
1434642e01fSmrg/**
1444642e01fSmrgInitialize a filter chain.
1454642e01fSmrgExpected result is a series of filters, each progressively more integrating.
1464642e01fSmrg
1474642e01fSmrgThis allows for two strategies: Either you have one filter which is reasonable
1484642e01fSmrgand is being coupled to account for fast-changing input, or you have 'one for
1494642e01fSmrgevery situation'. You might want to have tighter coupling then, e.g. 0.1.
1504642e01fSmrgIn the filter stats, you can see if a reasonable filter useage emerges.
1514642e01fSmrg*/
1524642e01fSmrgvoid
1534642e01fSmrgInitFilterChain(DeviceVelocityPtr s, float rdecay, float progression, int stages, int lutsize)
1544642e01fSmrg{
1554642e01fSmrg    int fn;
1564642e01fSmrg    if((stages > 1 && progression < 1.0f) || 0 == progression){
1574642e01fSmrg	ErrorF("(dix ptracc) invalid filter chain progression specified\n");
1584642e01fSmrg	return;
1594642e01fSmrg    }
1604642e01fSmrg    /* Block here to support runtime filter adjustment */
1614642e01fSmrg    OsBlockSignals();
1624642e01fSmrg    for(fn = 0; fn < MAX_VELOCITY_FILTERS; fn++){
1634642e01fSmrg	if(fn < stages){
1644642e01fSmrg	    InitFilterStage(&s->filters[fn], rdecay, lutsize);
1654642e01fSmrg	}else{
1664642e01fSmrg	    InitFilterStage(&s->filters[fn], 0, 0);
1674642e01fSmrg	}
1684642e01fSmrg	rdecay /= progression;
1694642e01fSmrg    }
1704642e01fSmrg    /* release again. Should the input loop be threaded, we also need
1714642e01fSmrg     * memory release here (in principle).
1724642e01fSmrg     */
1734642e01fSmrg    OsReleaseSignals();
1744642e01fSmrg}
1754642e01fSmrg
1764642e01fSmrg
1774642e01fSmrgvoid
1784642e01fSmrgCleanupFilterChain(DeviceVelocityPtr s)
1794642e01fSmrg{
1804642e01fSmrg    int fn;
1814642e01fSmrg
1824642e01fSmrg    for(fn = 0; fn < MAX_VELOCITY_FILTERS; fn++)
1834642e01fSmrg	InitFilterStage(&s->filters[fn], 0, 0);
1844642e01fSmrg}
1854642e01fSmrg
1864642e01fSmrgstatic inline void
1874642e01fSmrgStuffFilterChain(DeviceVelocityPtr s, float value)
1884642e01fSmrg{
1894642e01fSmrg    int fn;
1904642e01fSmrg
1914642e01fSmrg    for(fn = 0; fn < MAX_VELOCITY_FILTERS; fn++){
1924642e01fSmrg	if(s->filters[fn].rdecay != 0)
1934642e01fSmrg	    s->filters[fn].current = value;
1944642e01fSmrg	else break;
1954642e01fSmrg    }
1964642e01fSmrg}
1974642e01fSmrg
1984642e01fSmrg
1994642e01fSmrg/**
2004642e01fSmrg * Adjust weighting decay and lut for a stage
2014642e01fSmrg * The weight fn is designed so its integral 0->inf is unity, so we end
2024642e01fSmrg * up with a stable (basically IIR) filter. It always draws
2034642e01fSmrg * towards its more current input values, which have more weight the older
2044642e01fSmrg * the last input value is.
2054642e01fSmrg */
2064642e01fSmrgvoid
2074642e01fSmrgInitFilterStage(FilterStagePtr s, float rdecay, int lutsize)
2084642e01fSmrg{
2094642e01fSmrg    int x;
2104642e01fSmrg    float *newlut;
2114642e01fSmrg    float *oldlut;
2124642e01fSmrg
2134642e01fSmrg    s->fading_lut_size  = 0; /* prevent access */
2144642e01fSmrg
2154642e01fSmrg    if(lutsize > 0){
2164642e01fSmrg        newlut = xalloc (sizeof(float)* lutsize);
2174642e01fSmrg        if(!newlut)
2184642e01fSmrg            return;
2194642e01fSmrg        for(x = 0; x < lutsize; x++)
2204642e01fSmrg            newlut[x] = pow(0.5, ((float)x) * rdecay);
2214642e01fSmrg    }else{
2224642e01fSmrg        newlut = NULL;
2234642e01fSmrg    }
2244642e01fSmrg    oldlut = s->fading_lut;
2254642e01fSmrg    s->fading_lut = newlut;
2264642e01fSmrg    s->rdecay = rdecay;
2274642e01fSmrg    s->fading_lut_size = lutsize;
2284642e01fSmrg    s->current = 0;
2294642e01fSmrg    if(oldlut != NULL)
2304642e01fSmrg        xfree(oldlut);
2314642e01fSmrg}
2324642e01fSmrg
2334642e01fSmrg
2344642e01fSmrgstatic inline void
2354642e01fSmrgFeedFilterChain(DeviceVelocityPtr s, float value, int tdiff)
2364642e01fSmrg{
2374642e01fSmrg    int fn;
2384642e01fSmrg
2394642e01fSmrg    for(fn = 0; fn < MAX_VELOCITY_FILTERS; fn++){
2404642e01fSmrg	if(s->filters[fn].rdecay != 0)
2414642e01fSmrg	    FeedFilterStage(&s->filters[fn], value, tdiff);
2424642e01fSmrg	else break;
2434642e01fSmrg    }
2444642e01fSmrg}
2454642e01fSmrg
2464642e01fSmrg
2474642e01fSmrgstatic inline void
2484642e01fSmrgFeedFilterStage(FilterStagePtr s, float value, int tdiff){
2494642e01fSmrg    float fade;
2504642e01fSmrg    if(tdiff < s->fading_lut_size)
2514642e01fSmrg        fade = s->fading_lut[tdiff];
2524642e01fSmrg    else
2534642e01fSmrg        fade = pow(0.5, ((float)tdiff) * s->rdecay);
2544642e01fSmrg    s->current *= fade;    /* fade out old velocity */
2554642e01fSmrg    s->current += value * (1.0f - fade);    /* and add up current */
2564642e01fSmrg}
2574642e01fSmrg
2584642e01fSmrg/**
2594642e01fSmrg * Select the most filtered matching result. Also, the first
2604642e01fSmrg * mismatching filter may be set to value (coupling).
2614642e01fSmrg */
2624642e01fSmrgstatic inline float
2634642e01fSmrgQueryFilterChain(
2644642e01fSmrg    DeviceVelocityPtr s,
2654642e01fSmrg    float value)
2664642e01fSmrg{
2674642e01fSmrg    int fn, rfn = 0, cfn = -1;
2684642e01fSmrg    float cur, result = value;
2694642e01fSmrg
2704642e01fSmrg    /* try to retrieve most integrated result 'within range'
2714642e01fSmrg     * Assumption: filter are in order least to most integrating */
2724642e01fSmrg    for(fn = 0; fn < MAX_VELOCITY_FILTERS; fn++){
2734642e01fSmrg	if(0.0f == s->filters[fn].rdecay)
2744642e01fSmrg	    break;
2754642e01fSmrg	cur = s->filters[fn].current;
2764642e01fSmrg
2774642e01fSmrg	if (fabs(value - cur) <= (s->coupling * (value + cur))){
2784642e01fSmrg	    result = cur;
2794642e01fSmrg	    rfn = fn + 1; /*remember result determining filter */
2804642e01fSmrg	} else if(cfn == -1){
2814642e01fSmrg	    cfn = fn; /* remember first mismatching filter */
2824642e01fSmrg	}
2834642e01fSmrg    }
2844642e01fSmrg
2854642e01fSmrg    s->statistics.filter_usecount[rfn]++;
2864642e01fSmrg    DebugAccelF("(dix ptracc) result from stage %i,  input %.2f, output %.2f\n",
2874642e01fSmrg           rfn, value, result);
2884642e01fSmrg
2894642e01fSmrg    /* override first mismatching current (coupling) so the filter
2904642e01fSmrg     * catches up quickly. */
2914642e01fSmrg    if(cfn != -1)
2924642e01fSmrg        s->filters[cfn].current = result;
2934642e01fSmrg
2944642e01fSmrg    return result;
2954642e01fSmrg}
2964642e01fSmrg
2974642e01fSmrg/********************************
2984642e01fSmrg *  velocity computation
2994642e01fSmrg *******************************/
3004642e01fSmrg
3014642e01fSmrg/**
3024642e01fSmrg * return the axis if mickey is insignificant and axis-aligned,
3034642e01fSmrg * -1 otherwise
3044642e01fSmrg * 1 for x-axis
3054642e01fSmrg * 2 for y-axis
3064642e01fSmrg */
3074642e01fSmrgstatic inline short
3084642e01fSmrgGetAxis(int dx, int dy){
3094642e01fSmrg    if(dx == 0 || dy == 0){
3104642e01fSmrg        if(dx == 1 || dx == -1)
3114642e01fSmrg            return 1;
3124642e01fSmrg        if(dy == 1 || dy == -1)
3134642e01fSmrg            return 2;
3144642e01fSmrg        return -1;
3154642e01fSmrg    }else{
3164642e01fSmrg        return -1;
3174642e01fSmrg    }
3184642e01fSmrg}
3194642e01fSmrg
3204642e01fSmrg
3214642e01fSmrg/**
3224642e01fSmrg * Perform velocity approximation
3234642e01fSmrg * return true if non-visible state reset is suggested
3244642e01fSmrg */
3254642e01fSmrgstatic short
3264642e01fSmrgProcessVelocityData(
3274642e01fSmrg    DeviceVelocityPtr s,
3284642e01fSmrg    int dx,
3294642e01fSmrg    int dy,
3304642e01fSmrg    int time)
3314642e01fSmrg{
3324642e01fSmrg    float cvelocity;
3334642e01fSmrg
3344642e01fSmrg    int diff = time - s->lrm_time;
3354642e01fSmrg    int cur_ax, last_ax;
3364642e01fSmrg    short reset = (diff >= s->reset_time);
3374642e01fSmrg
3384642e01fSmrg    /* remember last round's result */
3394642e01fSmrg    s->last_velocity = s->velocity;
3404642e01fSmrg    cur_ax = GetAxis(dx, dy);
3414642e01fSmrg    last_ax = GetAxis(s->last_dx, s->last_dy);
3424642e01fSmrg
3434642e01fSmrg    if(cur_ax != last_ax && cur_ax != -1 && last_ax != -1 && !reset){
3444642e01fSmrg        /* correct for the error induced when diagonal movements are
3454642e01fSmrg           reported as alternating axis mickeys */
3464642e01fSmrg        dx += s->last_dx;
3474642e01fSmrg        dy += s->last_dy;
3484642e01fSmrg        diff += s->last_diff;
3494642e01fSmrg        s->last_diff = time - s->lrm_time; /* prevent repeating add-up */
3504642e01fSmrg        DebugAccelF("(dix ptracc) axial correction\n");
3514642e01fSmrg    }else{
3524642e01fSmrg        s->last_diff = diff;
3534642e01fSmrg    }
3544642e01fSmrg
3554642e01fSmrg    /*
3564642e01fSmrg     * cvelocity is not a real velocity yet, more a motion delta. constant
3574642e01fSmrg     * acceleration is multiplied here to make the velocity an on-screen
3584642e01fSmrg     * velocity (pix/t as opposed to [insert unit]/t). This is intended to
3594642e01fSmrg     * make multiple devices with widely varying ConstantDecelerations respond
3604642e01fSmrg     * similar to acceleration controls.
3614642e01fSmrg     */
3624642e01fSmrg    cvelocity = (float)sqrt(dx*dx + dy*dy) * s->const_acceleration;
3634642e01fSmrg
3644642e01fSmrg    s->lrm_time = time;
3654642e01fSmrg
3664642e01fSmrg    if (s->reset_time < 0 || diff < 0) { /* reset disabled or timer overrun? */
3674642e01fSmrg        /* simply set velocity from current movement, no reset. */
3684642e01fSmrg        s->velocity = cvelocity;
3694642e01fSmrg        return FALSE;
3704642e01fSmrg    }
3714642e01fSmrg
3724642e01fSmrg    if (diff == 0)
3734642e01fSmrg        diff = 1; /* prevent div-by-zero, though it shouldn't happen anyway*/
3744642e01fSmrg
3754642e01fSmrg    /* translate velocity to dots/ms (somewhat intractable in integers,
3764642e01fSmrg       so we multiply by some per-device adjustable factor) */
3774642e01fSmrg    cvelocity = cvelocity * s->corr_mul / (float)diff;
3784642e01fSmrg
3794642e01fSmrg    /* short-circuit: when nv-reset the rest can be skipped */
3804642e01fSmrg    if(reset == TRUE){
3814642e01fSmrg	/*
3824642e01fSmrg	 * we don't really have a velocity here, since diff includes inactive
3834642e01fSmrg	 * time. This is dealt with in ComputeAcceleration.
3844642e01fSmrg	 */
3854642e01fSmrg	StuffFilterChain(s, cvelocity);
3864642e01fSmrg	s->velocity = s->last_velocity = cvelocity;
3874642e01fSmrg	s->last_reset = TRUE;
3884642e01fSmrg	DebugAccelF("(dix ptracc) non-visible state reset\n");
3894642e01fSmrg	return TRUE;
3904642e01fSmrg    }
3914642e01fSmrg
3924642e01fSmrg    if(s->last_reset == TRUE){
3934642e01fSmrg	/*
3944642e01fSmrg	 * when here, we're probably processing the second mickey of a starting
3954642e01fSmrg	 * stroke. This happens to be the first time we can reasonably pretend
3964642e01fSmrg	 * that cvelocity is an actual velocity. Thus, to opt precision, we
3974642e01fSmrg	 * stuff that into the filter chain.
3984642e01fSmrg	 */
3994642e01fSmrg	s->last_reset = FALSE;
4004642e01fSmrg	DebugAccelF("(dix ptracc) after-reset vel:%.3f\n", cvelocity);
4014642e01fSmrg	StuffFilterChain(s, cvelocity);
4024642e01fSmrg	s->velocity = cvelocity;
4034642e01fSmrg	return FALSE;
4044642e01fSmrg    }
4054642e01fSmrg
4064642e01fSmrg    /* feed into filter chain */
4074642e01fSmrg    FeedFilterChain(s, cvelocity, diff);
4084642e01fSmrg
4094642e01fSmrg    /* perform coupling and decide final value */
4104642e01fSmrg    s->velocity = QueryFilterChain(s, cvelocity);
4114642e01fSmrg
4124642e01fSmrg    DebugAccelF("(dix ptracc) guess: vel=%.3f diff=%d   %i|%i|%i|%i|%i|%i|%i|%i|%i\n",
4134642e01fSmrg           s->velocity, diff,
4144642e01fSmrg           s->statistics.filter_usecount[0], s->statistics.filter_usecount[1],
4154642e01fSmrg           s->statistics.filter_usecount[2], s->statistics.filter_usecount[3],
4164642e01fSmrg           s->statistics.filter_usecount[4], s->statistics.filter_usecount[5],
4174642e01fSmrg           s->statistics.filter_usecount[6], s->statistics.filter_usecount[7],
4184642e01fSmrg           s->statistics.filter_usecount[8]);
4194642e01fSmrg    return FALSE;
4204642e01fSmrg}
4214642e01fSmrg
4224642e01fSmrg
4234642e01fSmrg/**
4244642e01fSmrg * this flattens significant ( > 1) mickeys a little bit for more steady
4254642e01fSmrg * constant-velocity response
4264642e01fSmrg */
4274642e01fSmrgstatic inline float
4284642e01fSmrgApplySimpleSoftening(int od, int d)
4294642e01fSmrg{
4304642e01fSmrg    float res = d;
4314642e01fSmrg    if (d <= 1 && d >= -1)
4324642e01fSmrg        return res;
4334642e01fSmrg    if (d > od)
4344642e01fSmrg        res -= 0.5;
4354642e01fSmrg    else if (d < od)
4364642e01fSmrg        res += 0.5;
4374642e01fSmrg    return res;
4384642e01fSmrg}
4394642e01fSmrg
4404642e01fSmrg
4414642e01fSmrgstatic void
4424642e01fSmrgApplySofteningAndConstantDeceleration(
4434642e01fSmrg        DeviceVelocityPtr s,
4444642e01fSmrg        int dx,
4454642e01fSmrg        int dy,
4464642e01fSmrg        float* fdx,
4474642e01fSmrg        float* fdy,
4484642e01fSmrg        short do_soften)
4494642e01fSmrg{
4504642e01fSmrg    if (do_soften && s->use_softening) {
4514642e01fSmrg        *fdx = ApplySimpleSoftening(s->last_dx, dx);
4524642e01fSmrg        *fdy = ApplySimpleSoftening(s->last_dy, dy);
4534642e01fSmrg    } else {
4544642e01fSmrg        *fdx = dx;
4554642e01fSmrg        *fdy = dy;
4564642e01fSmrg    }
4574642e01fSmrg
4584642e01fSmrg    *fdx *= s->const_acceleration;
4594642e01fSmrg    *fdy *= s->const_acceleration;
4604642e01fSmrg}
4614642e01fSmrg
4624642e01fSmrg/*
4634642e01fSmrg * compute the acceleration for given velocity and enforce min_acceleartion
4644642e01fSmrg */
4654642e01fSmrgstatic float
4664642e01fSmrgBasicComputeAcceleration(
4674642e01fSmrg    DeviceVelocityPtr pVel,
4684642e01fSmrg    float velocity,
4694642e01fSmrg    float threshold,
4704642e01fSmrg    float acc){
4714642e01fSmrg
4724642e01fSmrg    float result;
4734642e01fSmrg    result = pVel->Profile(pVel, velocity, threshold, acc);
4744642e01fSmrg
4754642e01fSmrg    /* enforce min_acceleration */
4764642e01fSmrg    if (result < pVel->min_acceleration)
4774642e01fSmrg	result = pVel->min_acceleration;
4784642e01fSmrg    return result;
4794642e01fSmrg}
4804642e01fSmrg
4814642e01fSmrg/**
4824642e01fSmrg * Compute acceleration. Takes into account averaging, nv-reset, etc.
4834642e01fSmrg */
4844642e01fSmrgstatic float
4854642e01fSmrgComputeAcceleration(
4864642e01fSmrg    DeviceVelocityPtr vel,
4874642e01fSmrg    float threshold,
4884642e01fSmrg    float acc){
4894642e01fSmrg    float res;
4904642e01fSmrg
4914642e01fSmrg    if(vel->last_reset){
4924642e01fSmrg	DebugAccelF("(dix ptracc) profile skipped\n");
4934642e01fSmrg        /*
4944642e01fSmrg         * This is intended to override the first estimate of a stroke,
4954642e01fSmrg         * which is too low (see ProcessVelocityData). 1 should make sure
4964642e01fSmrg         * the mickey is seen on screen.
4974642e01fSmrg         */
4984642e01fSmrg	return 1;
4994642e01fSmrg    }
5004642e01fSmrg
5014642e01fSmrg    if(vel->average_accel && vel->velocity != vel->last_velocity){
5024642e01fSmrg	/* use simpson's rule to average acceleration between
5034642e01fSmrg	 * current and previous velocity.
5044642e01fSmrg	 * Though being the more natural choice, it causes a minor delay
5054642e01fSmrg	 * in comparison, so it can be disabled. */
5064642e01fSmrg	res = BasicComputeAcceleration(vel, vel->velocity, threshold, acc);
5074642e01fSmrg	res += BasicComputeAcceleration(vel, vel->last_velocity, threshold, acc);
5084642e01fSmrg	res += 4.0f * BasicComputeAcceleration(vel,
5094642e01fSmrg	                   (vel->last_velocity + vel->velocity) / 2,
5104642e01fSmrg	                   threshold, acc);
5114642e01fSmrg	res /= 6.0f;
5124642e01fSmrg	DebugAccelF("(dix ptracc) profile average [%.2f ... %.2f] is %.3f\n",
5134642e01fSmrg	            vel->velocity, vel->last_velocity, res);
5144642e01fSmrg        return res;
5154642e01fSmrg    }else{
5164642e01fSmrg	res = BasicComputeAcceleration(vel, vel->velocity, threshold, acc);
5174642e01fSmrg	DebugAccelF("(dix ptracc) profile sample [%.2f] is %.3f\n",
5184642e01fSmrg               vel->velocity, res);
5194642e01fSmrg	return res;
5204642e01fSmrg    }
5214642e01fSmrg}
5224642e01fSmrg
5234642e01fSmrg
5244642e01fSmrg/*****************************************
5254642e01fSmrg *  Acceleration functions and profiles
5264642e01fSmrg ****************************************/
5274642e01fSmrg
5284642e01fSmrg/**
5294642e01fSmrg * Polynomial function similar previous one, but with f(1) = 1
5304642e01fSmrg */
5314642e01fSmrgstatic float
5324642e01fSmrgPolynomialAccelerationProfile(
5334642e01fSmrg    DeviceVelocityPtr pVel,
5344642e01fSmrg    float velocity,
5354642e01fSmrg    float ignored,
5364642e01fSmrg    float acc)
5374642e01fSmrg{
5384642e01fSmrg   return pow(velocity, (acc - 1.0) * 0.5);
5394642e01fSmrg}
5404642e01fSmrg
5414642e01fSmrg
5424642e01fSmrg/**
5434642e01fSmrg * returns acceleration for velocity.
5444642e01fSmrg * This profile selects the two functions like the old scheme did
5454642e01fSmrg */
5464642e01fSmrgstatic float
5474642e01fSmrgClassicProfile(
5484642e01fSmrg    DeviceVelocityPtr pVel,
5494642e01fSmrg    float velocity,
5504642e01fSmrg    float threshold,
5514642e01fSmrg    float acc)
5524642e01fSmrg{
5534642e01fSmrg    if (threshold) {
5544642e01fSmrg	return SimpleSmoothProfile (pVel,
5554642e01fSmrg	                            velocity,
5564642e01fSmrg                                    threshold,
5574642e01fSmrg                                    acc);
5584642e01fSmrg    } else {
5594642e01fSmrg	return PolynomialAccelerationProfile (pVel,
5604642e01fSmrg	                                      velocity,
5614642e01fSmrg                                              0,
5624642e01fSmrg                                              acc);
5634642e01fSmrg    }
5644642e01fSmrg}
5654642e01fSmrg
5664642e01fSmrg
5674642e01fSmrg/**
5684642e01fSmrg * Power profile
5694642e01fSmrg * This has a completely smooth transition curve, i.e. no jumps in the
5704642e01fSmrg * derivatives.
5714642e01fSmrg *
5724642e01fSmrg * This has the expense of overall response dependency on min-acceleration.
5734642e01fSmrg * In effect, min_acceleration mimics const_acceleration in this profile.
5744642e01fSmrg */
5754642e01fSmrgstatic float
5764642e01fSmrgPowerProfile(
5774642e01fSmrg    DeviceVelocityPtr pVel,
5784642e01fSmrg    float velocity,
5794642e01fSmrg    float threshold,
5804642e01fSmrg    float acc)
5814642e01fSmrg{
5824642e01fSmrg    float vel_dist;
5834642e01fSmrg
5844642e01fSmrg    acc = (acc-1.0) * 0.1f + 1.0; /* without this, acc of 2 is unuseable */
5854642e01fSmrg
5864642e01fSmrg    if (velocity <= threshold)
5874642e01fSmrg        return pVel->min_acceleration;
5884642e01fSmrg    vel_dist = velocity - threshold;
5894642e01fSmrg    return (pow(acc, vel_dist)) * pVel->min_acceleration;
5904642e01fSmrg}
5914642e01fSmrg
5924642e01fSmrg
5934642e01fSmrg/**
5944642e01fSmrg * just a smooth function in [0..1] -> [0..1]
5954642e01fSmrg *  - point symmetry at 0.5
5964642e01fSmrg *  - f'(0) = f'(1) = 0
5974642e01fSmrg *  - starts faster than a sinoid
5984642e01fSmrg *  - smoothness C1 (Cinf if you dare to ignore endpoints)
5994642e01fSmrg */
6004642e01fSmrgstatic inline float
6014642e01fSmrgCalcPenumbralGradient(float x){
6024642e01fSmrg    x *= 2.0f;
6034642e01fSmrg    x -= 1.0f;
6044642e01fSmrg    return 0.5f + (x * sqrt(1.0f - x*x) + asin(x))/M_PI;
6054642e01fSmrg}
6064642e01fSmrg
6074642e01fSmrg
6084642e01fSmrg/**
6094642e01fSmrg * acceleration function similar to classic accelerated/unaccelerated,
6104642e01fSmrg * but with smooth transition in between (and towards zero for adaptive dec.).
6114642e01fSmrg */
6124642e01fSmrgstatic float
6134642e01fSmrgSimpleSmoothProfile(
6144642e01fSmrg    DeviceVelocityPtr pVel,
6154642e01fSmrg    float velocity,
6164642e01fSmrg    float threshold,
6174642e01fSmrg    float acc)
6184642e01fSmrg{
6194642e01fSmrg    if(velocity < 1.0f)
6204642e01fSmrg        return CalcPenumbralGradient(0.5 + velocity*0.5) * 2.0f - 1.0f;
6214642e01fSmrg    if(threshold < 1.0f)
6224642e01fSmrg        threshold = 1.0f;
6234642e01fSmrg    if (velocity <= threshold)
6244642e01fSmrg        return 1;
6254642e01fSmrg    velocity /= threshold;
6264642e01fSmrg    if (velocity >= acc)
6274642e01fSmrg        return acc;
6284642e01fSmrg    else
6294642e01fSmrg        return 1.0f + (CalcPenumbralGradient(velocity/acc) * (acc - 1.0f));
6304642e01fSmrg}
6314642e01fSmrg
6324642e01fSmrg
6334642e01fSmrg/**
6344642e01fSmrg * This profile uses the first half of the penumbral gradient as a start
6354642e01fSmrg * and then scales linearly.
6364642e01fSmrg */
6374642e01fSmrgstatic float
6384642e01fSmrgSmoothLinearProfile(
6394642e01fSmrg    DeviceVelocityPtr pVel,
6404642e01fSmrg    float velocity,
6414642e01fSmrg    float threshold,
6424642e01fSmrg    float acc)
6434642e01fSmrg{
6444642e01fSmrg    float res, nv;
6454642e01fSmrg
6464642e01fSmrg    if(acc > 1.0f)
6474642e01fSmrg        acc -= 1.0f; /*this is so acc = 1 is no acceleration */
6484642e01fSmrg    else
6494642e01fSmrg        return 1.0f;
6504642e01fSmrg
6514642e01fSmrg    nv = (velocity - threshold) * acc * 0.5f;
6524642e01fSmrg
6534642e01fSmrg    if(nv < 0){
6544642e01fSmrg        res = 0;
6554642e01fSmrg    }else if(nv < 2){
6564642e01fSmrg        res = CalcPenumbralGradient(nv*0.25f)*2.0f;
6574642e01fSmrg    }else{
6584642e01fSmrg        nv -= 2.0f;
6594642e01fSmrg        res = nv * 2.0f / M_PI  /* steepness of gradient at 0.5 */
6604642e01fSmrg              + 1.0f; /* gradient crosses 2|1 */
6614642e01fSmrg    }
6624642e01fSmrg    res += pVel->min_acceleration;
6634642e01fSmrg    return res;
6644642e01fSmrg}
6654642e01fSmrg
6664642e01fSmrg
6674642e01fSmrgstatic float
6684642e01fSmrgLinearProfile(
6694642e01fSmrg    DeviceVelocityPtr pVel,
6704642e01fSmrg    float velocity,
6714642e01fSmrg    float threshold,
6724642e01fSmrg    float acc)
6734642e01fSmrg{
6744642e01fSmrg    return acc * velocity;
6754642e01fSmrg}
6764642e01fSmrg
6774642e01fSmrg
6784642e01fSmrg/**
6794642e01fSmrg * Set the profile by number.
6804642e01fSmrg * Intended to make profiles exchangeable at runtime.
6814642e01fSmrg * If you created a profile, give it a number here and in the header to
6824642e01fSmrg * make it selectable. In case some profile-specific init is needed, here
6834642e01fSmrg * would be a good place, since FreeVelocityData() also calls this with -1.
6844642e01fSmrg * returns FALSE (0) if profile number is unavailable.
6854642e01fSmrg */
6864642e01fSmrg_X_EXPORT int
6874642e01fSmrgSetAccelerationProfile(
6884642e01fSmrg    DeviceVelocityPtr s,
6894642e01fSmrg    int profile_num)
6904642e01fSmrg{
6914642e01fSmrg    PointerAccelerationProfileFunc profile;
6924642e01fSmrg    switch(profile_num){
6934642e01fSmrg        case -1:
6944642e01fSmrg            profile = NULL;  /* Special case to uninit properly */
6954642e01fSmrg            break;
6964642e01fSmrg        case AccelProfileClassic:
6974642e01fSmrg            profile = ClassicProfile;
6984642e01fSmrg            break;
6994642e01fSmrg        case AccelProfileDeviceSpecific:
7004642e01fSmrg            if(NULL == s->deviceSpecificProfile)
7014642e01fSmrg        	return FALSE;
7024642e01fSmrg            profile = s->deviceSpecificProfile;
7034642e01fSmrg            break;
7044642e01fSmrg        case AccelProfilePolynomial:
7054642e01fSmrg            profile = PolynomialAccelerationProfile;
7064642e01fSmrg            break;
7074642e01fSmrg        case AccelProfileSmoothLinear:
7084642e01fSmrg            profile = SmoothLinearProfile;
7094642e01fSmrg            break;
7104642e01fSmrg        case AccelProfileSimple:
7114642e01fSmrg            profile = SimpleSmoothProfile;
7124642e01fSmrg            break;
7134642e01fSmrg        case AccelProfilePower:
7144642e01fSmrg            profile = PowerProfile;
7154642e01fSmrg            break;
7164642e01fSmrg        case AccelProfileLinear:
7174642e01fSmrg            profile = LinearProfile;
7184642e01fSmrg            break;
7194642e01fSmrg        case AccelProfileReserved:
7204642e01fSmrg            /* reserved for future use, e.g. a user-defined profile */
7214642e01fSmrg        default:
7224642e01fSmrg            return FALSE;
7234642e01fSmrg    }
7244642e01fSmrg    if(s->profile_private != NULL){
7254642e01fSmrg        /* Here one could free old profile-private data */
7264642e01fSmrg        xfree(s->profile_private);
7274642e01fSmrg        s->profile_private = NULL;
7284642e01fSmrg    }
7294642e01fSmrg    /* Here one could init profile-private data */
7304642e01fSmrg    s->Profile = profile;
7314642e01fSmrg    s->statistics.profile_number = profile_num;
7324642e01fSmrg    return TRUE;
7334642e01fSmrg}
7344642e01fSmrg
7354642e01fSmrg/**********************************************
7364642e01fSmrg * driver interaction
7374642e01fSmrg **********************************************/
7384642e01fSmrg
7394642e01fSmrg
7404642e01fSmrg/**
7414642e01fSmrg * device-specific profile
7424642e01fSmrg *
7434642e01fSmrg * The device-specific profile is intended as a hook for a driver
7444642e01fSmrg * which may want to provide an own acceleration profile.
7454642e01fSmrg * It should not rely on profile-private data, instead
7464642e01fSmrg * it should do init/uninit in the driver (ie. with DEVICE_INIT and friends).
7474642e01fSmrg * Users may override or choose it.
7484642e01fSmrg */
7494642e01fSmrg_X_EXPORT void
7504642e01fSmrgSetDeviceSpecificAccelerationProfile(
7514642e01fSmrg        DeviceVelocityPtr s,
7524642e01fSmrg        PointerAccelerationProfileFunc profile)
7534642e01fSmrg{
7544642e01fSmrg    if(s)
7554642e01fSmrg	s->deviceSpecificProfile = profile;
7564642e01fSmrg}
7574642e01fSmrg
7584642e01fSmrg/**
7594642e01fSmrg * Use this function to obtain a DeviceVelocityPtr for a device. Will return NULL if
7604642e01fSmrg * the predictable acceleration scheme is not in effect.
7614642e01fSmrg */
7624642e01fSmrg_X_EXPORT DeviceVelocityPtr
7634642e01fSmrgGetDevicePredictableAccelData(
7644642e01fSmrg	DeviceIntPtr pDev)
7654642e01fSmrg{
7664642e01fSmrg    /*sanity check*/
7674642e01fSmrg    if(!pDev){
7684642e01fSmrg	ErrorF("[dix] accel: DeviceIntPtr was NULL");
7694642e01fSmrg	return NULL;
7704642e01fSmrg    }
7714642e01fSmrg    if( pDev->valuator &&
7724642e01fSmrg	pDev->valuator->accelScheme.AccelSchemeProc ==
7734642e01fSmrg	    acceleratePointerPredictable &&
7744642e01fSmrg	pDev->valuator->accelScheme.accelData != NULL){
7754642e01fSmrg
7764642e01fSmrg	return (DeviceVelocityPtr)pDev->valuator->accelScheme.accelData;
7774642e01fSmrg    }
7784642e01fSmrg    return NULL;
7794642e01fSmrg}
7804642e01fSmrg
7814642e01fSmrg/********************************
7824642e01fSmrg *  acceleration schemes
7834642e01fSmrg *******************************/
7844642e01fSmrg
7854642e01fSmrg/**
7864642e01fSmrg * Modifies valuators in-place.
7874642e01fSmrg * This version employs a velocity approximation algorithm to
7884642e01fSmrg * enable fine-grained predictable acceleration profiles.
7894642e01fSmrg */
7904642e01fSmrgvoid
7914642e01fSmrgacceleratePointerPredictable(
7924642e01fSmrg    DeviceIntPtr pDev,
7934642e01fSmrg    int first_valuator,
7944642e01fSmrg    int num_valuators,
7954642e01fSmrg    int *valuators,
7964642e01fSmrg    int evtime)
7974642e01fSmrg{
7984642e01fSmrg    float mult = 0.0;
7994642e01fSmrg    int dx = 0, dy = 0;
8004642e01fSmrg    int *px = NULL, *py = NULL;
8014642e01fSmrg    DeviceVelocityPtr velocitydata =
8024642e01fSmrg	(DeviceVelocityPtr) pDev->valuator->accelScheme.accelData;
8034642e01fSmrg    float fdx, fdy; /* no need to init */
8044642e01fSmrg
8054642e01fSmrg    if (!num_valuators || !valuators || !velocitydata)
8064642e01fSmrg        return;
8074642e01fSmrg
8084642e01fSmrg    if (first_valuator == 0) {
8094642e01fSmrg        dx = valuators[0];
8104642e01fSmrg        px = &valuators[0];
8114642e01fSmrg    }
8124642e01fSmrg    if (first_valuator <= 1 && num_valuators >= (2 - first_valuator)) {
8134642e01fSmrg        dy = valuators[1 - first_valuator];
8144642e01fSmrg        py = &valuators[1 - first_valuator];
8154642e01fSmrg    }
8164642e01fSmrg
8174642e01fSmrg    if (dx || dy){
8184642e01fSmrg        /* reset nonvisible state? */
8194642e01fSmrg        if (ProcessVelocityData(velocitydata, dx , dy, evtime)) {
8204642e01fSmrg            /* set to center of pixel. makes sense as long as there are no
8214642e01fSmrg             * means of passing on sub-pixel values.
8224642e01fSmrg             */
8234642e01fSmrg            pDev->last.remainder[0] = pDev->last.remainder[1] = 0.5f;
8244642e01fSmrg            /* prevent softening (somewhat quirky solution,
8254642e01fSmrg            as it depends on the algorithm) */
8264642e01fSmrg            velocitydata->last_dx = dx;
8274642e01fSmrg            velocitydata->last_dy = dy;
8284642e01fSmrg        }
8294642e01fSmrg
8304642e01fSmrg        if (pDev->ptrfeed && pDev->ptrfeed->ctrl.num) {
8314642e01fSmrg            /* invoke acceleration profile to determine acceleration */
8324642e01fSmrg            mult = ComputeAcceleration (velocitydata,
8334642e01fSmrg					pDev->ptrfeed->ctrl.threshold,
8344642e01fSmrg					(float)pDev->ptrfeed->ctrl.num /
8354642e01fSmrg					(float)pDev->ptrfeed->ctrl.den);
8364642e01fSmrg
8374642e01fSmrg            if(mult != 1.0 || velocitydata->const_acceleration != 1.0) {
8384642e01fSmrg                ApplySofteningAndConstantDeceleration( velocitydata,
8394642e01fSmrg                                                       dx, dy,
8404642e01fSmrg                                                       &fdx, &fdy,
8414642e01fSmrg                                                       mult > 1.0);
8424642e01fSmrg                if (dx) {
8434642e01fSmrg                    pDev->last.remainder[0] = mult * fdx + pDev->last.remainder[0];
8444642e01fSmrg                    *px = (int)pDev->last.remainder[0];
8454642e01fSmrg                    pDev->last.remainder[0] = pDev->last.remainder[0] - (float)*px;
8464642e01fSmrg                }
8474642e01fSmrg                if (dy) {
8484642e01fSmrg                    pDev->last.remainder[1] = mult * fdy + pDev->last.remainder[1];
8494642e01fSmrg                    *py = (int)pDev->last.remainder[1];
8504642e01fSmrg                    pDev->last.remainder[1] = pDev->last.remainder[1] - (float)*py;
8514642e01fSmrg                }
8524642e01fSmrg            }
8534642e01fSmrg        }
8544642e01fSmrg    }
8554642e01fSmrg    /* remember last motion delta (for softening/slow movement treatment) */
8564642e01fSmrg    velocitydata->last_dx = dx;
8574642e01fSmrg    velocitydata->last_dy = dy;
8584642e01fSmrg}
8594642e01fSmrg
8604642e01fSmrg
8614642e01fSmrg
8624642e01fSmrg/**
8634642e01fSmrg * Originally a part of xf86PostMotionEvent; modifies valuators
8644642e01fSmrg * in-place. Retained mostly for embedded scenarios.
8654642e01fSmrg */
8664642e01fSmrgvoid
8674642e01fSmrgacceleratePointerLightweight(
8684642e01fSmrg    DeviceIntPtr pDev,
8694642e01fSmrg    int first_valuator,
8704642e01fSmrg    int num_valuators,
8714642e01fSmrg    int *valuators,
8724642e01fSmrg    int ignored)
8734642e01fSmrg{
8744642e01fSmrg    float mult = 0.0;
8754642e01fSmrg    int dx = 0, dy = 0;
8764642e01fSmrg    int *px = NULL, *py = NULL;
8774642e01fSmrg
8784642e01fSmrg    if (!num_valuators || !valuators)
8794642e01fSmrg        return;
8804642e01fSmrg
8814642e01fSmrg    if (first_valuator == 0) {
8824642e01fSmrg        dx = valuators[0];
8834642e01fSmrg        px = &valuators[0];
8844642e01fSmrg    }
8854642e01fSmrg    if (first_valuator <= 1 && num_valuators >= (2 - first_valuator)) {
8864642e01fSmrg        dy = valuators[1 - first_valuator];
8874642e01fSmrg        py = &valuators[1 - first_valuator];
8884642e01fSmrg    }
8894642e01fSmrg
8904642e01fSmrg    if (!dx && !dy)
8914642e01fSmrg        return;
8924642e01fSmrg
8934642e01fSmrg    if (pDev->ptrfeed && pDev->ptrfeed->ctrl.num) {
8944642e01fSmrg        /* modeled from xf86Events.c */
8954642e01fSmrg        if (pDev->ptrfeed->ctrl.threshold) {
8964642e01fSmrg            if ((abs(dx) + abs(dy)) >= pDev->ptrfeed->ctrl.threshold) {
8974642e01fSmrg                pDev->last.remainder[0] = ((float)dx *
8984642e01fSmrg                                             (float)(pDev->ptrfeed->ctrl.num)) /
8994642e01fSmrg                                             (float)(pDev->ptrfeed->ctrl.den) +
9004642e01fSmrg                                            pDev->last.remainder[0];
9014642e01fSmrg                if (px) {
9024642e01fSmrg                    *px = (int)pDev->last.remainder[0];
9034642e01fSmrg                    pDev->last.remainder[0] = pDev->last.remainder[0] -
9044642e01fSmrg                                                (float)(*px);
9054642e01fSmrg                }
9064642e01fSmrg
9074642e01fSmrg                pDev->last.remainder[1] = ((float)dy *
9084642e01fSmrg                                             (float)(pDev->ptrfeed->ctrl.num)) /
9094642e01fSmrg                                             (float)(pDev->ptrfeed->ctrl.den) +
9104642e01fSmrg                                            pDev->last.remainder[1];
9114642e01fSmrg                if (py) {
9124642e01fSmrg                    *py = (int)pDev->last.remainder[1];
9134642e01fSmrg                    pDev->last.remainder[1] = pDev->last.remainder[1] -
9144642e01fSmrg                                                (float)(*py);
9154642e01fSmrg                }
9164642e01fSmrg            }
9174642e01fSmrg        }
9184642e01fSmrg        else {
9194642e01fSmrg	    mult = pow((float)dx * (float)dx + (float)dy * (float)dy,
9204642e01fSmrg                       ((float)(pDev->ptrfeed->ctrl.num) /
9214642e01fSmrg                        (float)(pDev->ptrfeed->ctrl.den) - 1.0) /
9224642e01fSmrg                       2.0) / 2.0;
9234642e01fSmrg            if (dx) {
9244642e01fSmrg                pDev->last.remainder[0] = mult * (float)dx +
9254642e01fSmrg                                            pDev->last.remainder[0];
9264642e01fSmrg                *px = (int)pDev->last.remainder[0];
9274642e01fSmrg                pDev->last.remainder[0] = pDev->last.remainder[0] -
9284642e01fSmrg                                            (float)(*px);
9294642e01fSmrg            }
9304642e01fSmrg            if (dy) {
9314642e01fSmrg                pDev->last.remainder[1] = mult * (float)dy +
9324642e01fSmrg                                            pDev->last.remainder[1];
9334642e01fSmrg                *py = (int)pDev->last.remainder[1];
9344642e01fSmrg                pDev->last.remainder[1] = pDev->last.remainder[1] -
9354642e01fSmrg                                            (float)(*py);
9364642e01fSmrg            }
9374642e01fSmrg        }
9384642e01fSmrg    }
9394642e01fSmrg}
940