From 982b0250255624e6bdf8782dcd2e7cb512f0bd4d Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Tue, 20 Apr 2021 22:39:00 +0300 Subject: iOS: Fixed momentum scroll timing The scrolling stutters were being caused by timing and not slow text rendering. Now the momentum scroll is locked to display refresh rate. --- src/ios.h | 1 + src/ios.m | 17 +++++++++++++++-- src/ui/touch.c | 46 +++++++++++++++++++++++++++++++--------------- 3 files changed, 47 insertions(+), 17 deletions(-) diff --git a/src/ios.h b/src/ios.h index 29669a26..ba462834 100644 --- a/src/ios.h +++ b/src/ios.h @@ -38,3 +38,4 @@ void exportDownloadedFile_iOS(const iString *path); iBool isPhone_iOS (void); void safeAreaInsets_iOS (float *left, float *top, float *right, float *bottom); +int displayRefreshRate_iOS (void); diff --git a/src/ios.m b/src/ios.m index 9aca5a19..58557ef5 100644 --- a/src/ios.m +++ b/src/ios.m @@ -33,11 +33,20 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ static iBool isSystemDarkMode_ = iFalse; static iBool isPhone_ = iFalse; -static UIViewController *viewController_(iWindow *window) { +static UIWindow *uiWindow_(iWindow *window) { SDL_SysWMinfo wm; SDL_VERSION(&wm.version); if (SDL_GetWindowWMInfo(window->win, &wm)) { - return wm.info.uikit.window.rootViewController; + return wm.info.uikit.window; + } + iAssert(false); + return NULL; +} + +static UIViewController *viewController_(iWindow *window) { + UIWindow *uiWin = uiWindow_(window); + if (uiWin) { + return uiWin.rootViewController; } iAssert(false); return NULL; @@ -248,6 +257,10 @@ iBool isPhone_iOS(void) { return isPhone_; } +int displayRefreshRate_iOS(void) { + return uiWindow_(get_Window()).screen.maximumFramesPerSecond; +} + void setupWindow_iOS(iWindow *window) { UIViewController *ctl = viewController_(window); isSystemDarkMode_ = isDarkMode_(window); diff --git a/src/ui/touch.c b/src/ui/touch.c index 4498efae..8a532821 100644 --- a/src/ui/touch.c +++ b/src/ui/touch.c @@ -99,6 +99,8 @@ struct Impl_TouchState { iArray *touches; iArray *pinches; iArray *moms; + double stepDurationMs; + double momFrictionPerStep; double lastMomTime; iInt2 currentTouchPos; /* for emulating SDL_GetMouseState() */ }; @@ -107,10 +109,15 @@ static iTouchState *touchState_(void) { static iTouchState state_; iTouchState *d = &state_; if (!d->touches) { - d->touches = new_Array(sizeof(iTouch)); - d->pinches = new_Array(sizeof(iPinch)); - d->moms = new_Array(sizeof(iMomentum)); - d->lastMomTime = SDL_GetTicks(); + d->touches = new_Array(sizeof(iTouch)); + d->pinches = new_Array(sizeof(iPinch)); + d->moms = new_Array(sizeof(iMomentum)); + d->lastMomTime = 0.0; + d->stepDurationMs = 1000.0 / 60.0; /* TODO: Ask SDL about the display refresh rate. */ +#if defined (iPlatformAppleMobile) + d->stepDurationMs = 1000.0 / (double) displayRefreshRate_iOS(); +#endif + d->momFrictionPerStep = pow(0.985, 120.0 / (1000.0 / d->stepDurationMs)); } return d; } @@ -229,10 +236,16 @@ static void dispatchNotification_Touch_(const iTouch *d, int code) { } } +iLocalDef double accurateTicks_(void) { + const uint64_t freq = SDL_GetPerformanceFrequency(); + const uint64_t count = SDL_GetPerformanceCounter(); + return 1000.0 * (double) count / (double) freq; +} + static void update_TouchState_(void *ptr) { iTouchState *d = ptr; - const uint32_t nowTime = SDL_GetTicks(); /* Check for long presses to simulate right clicks. */ + const uint32_t nowTime = SDL_GetTicks(); iForEach(Array, i, d->touches) { iTouch *touch = i.value; if (touch->pinchId || touch->isTouchDrag) { @@ -275,14 +288,17 @@ static void update_TouchState_(void *ptr) { } /* Update/cancel momentum scrolling. */ { const float minSpeed = 15.0f; - const float momFriction = 0.985f; /* per step */ - const float stepDurationMs = 1000.0f / 120.0f; - double momAvailMs = nowTime - d->lastMomTime; - int numSteps = (int) (momAvailMs / stepDurationMs); - d->lastMomTime += numSteps * stepDurationMs; + if (d->lastMomTime < 0.001) { + d->lastMomTime = accurateTicks_(); + } + const double momAvailMs = accurateTicks_() - d->lastMomTime; + /* Display refresh is vsynced and we'll be here at most once per frame. + However, we may come here TOO early, which would cause a hiccup in the scrolling, + so always do at least one step. */ + int numSteps = iMax(1, momAvailMs / d->stepDurationMs); + d->lastMomTime += numSteps * d->stepDurationMs; numSteps = iMin(numSteps, 10); /* don't spend too much time here */ - // printf("mom steps:%d\n", numSteps); -// iWindow *window = get_Window(); +// printf("mom steps:%d\n", numSteps); iForEach(Array, m, d->moms) { if (numSteps == 0) break; iMomentum *mom = m.value; @@ -291,8 +307,8 @@ static void update_TouchState_(void *ptr) { continue; } for (int step = 0; step < numSteps; step++) { - mulvf_F3(&mom->velocity, momFriction); - addv_F3(&mom->accum, mulf_F3(mom->velocity, stepDurationMs / 1000.0f)); + mulvf_F3(&mom->velocity, d->momFrictionPerStep); + addv_F3(&mom->accum, mulf_F3(mom->velocity, d->stepDurationMs / 1000.0f)); } const iInt2 pixels = initF3_I2(mom->accum); if (pixels.x || pixels.y) { @@ -664,7 +680,7 @@ iBool processEvent_Touch(const SDL_Event *ev) { .velocity = velocity }; if (isEmpty_Array(d->moms)) { - d->lastMomTime = nowTime; + d->lastMomTime = accurateTicks_(); } pushBack_Array(d->moms, &mom); //dispatchMotion_Touch_(touch->startPos, 0); -- cgit v1.2.3