diff options
Diffstat (limited to 'widget/gonk/GeckoTouchDispatcher.cpp')
-rw-r--r-- | widget/gonk/GeckoTouchDispatcher.cpp | 358 |
1 files changed, 358 insertions, 0 deletions
diff --git a/widget/gonk/GeckoTouchDispatcher.cpp b/widget/gonk/GeckoTouchDispatcher.cpp new file mode 100644 index 000000000..0b18c91a1 --- /dev/null +++ b/widget/gonk/GeckoTouchDispatcher.cpp @@ -0,0 +1,358 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sts=2 et sw=2 tw=80: */ +/* Copyright 2014 Mozilla Foundation and Mozilla contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "FrameMetrics.h" +#include "GeckoProfiler.h" +#include "GeckoTouchDispatcher.h" +#include "InputData.h" +#include "ProfilerMarkers.h" +#include "base/basictypes.h" +#include "gfxPrefs.h" +#include "libui/Input.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Mutex.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/TouchEvents.h" +#include "mozilla/dom/Touch.h" +#include "mozilla/layers/APZThreadUtils.h" +#include "mozilla/layers/CompositorBridgeParent.h" +#include "nsAppShell.h" +#include "nsDebug.h" +#include "nsThreadUtils.h" +#include "nsWindow.h" +#include <sys/types.h> +#include <unistd.h> +#include <utils/Timers.h> + +#undef LOG +#define LOG(args...) \ + __android_log_print(ANDROID_LOG_INFO, "Gonk" , ## args) + +// uncomment to print log resample data +// #define LOG_RESAMPLE_DATA 1 + +namespace mozilla { + +// Amount of time in MS before an input is considered expired. +static const uint64_t kInputExpirationThresholdMs = 1000; + +static StaticRefPtr<GeckoTouchDispatcher> sTouchDispatcher; + +/* static */ GeckoTouchDispatcher* +GeckoTouchDispatcher::GetInstance() +{ + if (!sTouchDispatcher) { + sTouchDispatcher = new GeckoTouchDispatcher(); + ClearOnShutdown(&sTouchDispatcher); + } + return sTouchDispatcher; +} + +GeckoTouchDispatcher::GeckoTouchDispatcher() + : mTouchQueueLock("GeckoTouchDispatcher::mTouchQueueLock") + , mHavePendingTouchMoves(false) + , mInflightNonMoveEvents(0) + , mTouchEventsFiltered(false) +{ + // Since GeckoTouchDispatcher is initialized when input is initialized + // and reads gfxPrefs, it is the first thing to touch gfxPrefs. + // The first thing to touch gfxPrefs MUST occur on the main thread and init + // the singleton + MOZ_ASSERT(sTouchDispatcher == nullptr); + MOZ_ASSERT(NS_IsMainThread()); + gfxPrefs::GetSingleton(); + + mEnabledUniformityInfo = gfxPrefs::UniformityInfo(); + mVsyncAdjust = TimeDuration::FromMilliseconds(gfxPrefs::TouchVsyncSampleAdjust()); + mMaxPredict = TimeDuration::FromMilliseconds(gfxPrefs::TouchResampleMaxPredict()); + mMinDelta = TimeDuration::FromMilliseconds(gfxPrefs::TouchResampleMinDelta()); + mOldTouchThreshold = TimeDuration::FromMilliseconds(gfxPrefs::TouchResampleOldTouchThreshold()); + mDelayedVsyncThreshold = TimeDuration::FromMilliseconds(gfxPrefs::TouchResampleVsyncDelayThreshold()); +} + +void +GeckoTouchDispatcher::SetCompositorVsyncScheduler(mozilla::layers::CompositorVsyncScheduler *aObserver) +{ + MOZ_ASSERT(NS_IsMainThread()); + // We assume on b2g that there is only 1 CompositorBridgeParent + MOZ_ASSERT(mCompositorVsyncScheduler == nullptr); + mCompositorVsyncScheduler = aObserver; +} + +void +GeckoTouchDispatcher::NotifyVsync(TimeStamp aVsyncTimestamp) +{ + layers::APZThreadUtils::AssertOnControllerThread(); + DispatchTouchMoveEvents(aVsyncTimestamp); +} + +// Touch data timestamps are in milliseconds, aEventTime is in nanoseconds +void +GeckoTouchDispatcher::NotifyTouch(MultiTouchInput& aTouch, TimeStamp aEventTime) +{ + if (mCompositorVsyncScheduler) { + mCompositorVsyncScheduler->SetNeedsComposite(); + } + + if (aTouch.mType == MultiTouchInput::MULTITOUCH_MOVE) { + MutexAutoLock lock(mTouchQueueLock); + if (mInflightNonMoveEvents > 0) { + // If we have any pending non-move events, we shouldn't resample the + // move events because we might end up dispatching events out of order. + // Instead, fall back to a non-resampling in-order dispatch until we're + // done processing the non-move events. + layers::APZThreadUtils::RunOnControllerThread(NewRunnableMethod<MultiTouchInput>( + this, &GeckoTouchDispatcher::DispatchTouchEvent, aTouch)); + return; + } + + mTouchMoveEvents.push_back(aTouch); + mHavePendingTouchMoves = true; + } else { + { // scope lock + MutexAutoLock lock(mTouchQueueLock); + mInflightNonMoveEvents++; + } + layers::APZThreadUtils::RunOnControllerThread(NewRunnableMethod<MultiTouchInput>( + this, &GeckoTouchDispatcher::DispatchTouchNonMoveEvent, aTouch)); + } +} + +void +GeckoTouchDispatcher::DispatchTouchNonMoveEvent(MultiTouchInput aInput) +{ + layers::APZThreadUtils::AssertOnControllerThread(); + + // Flush pending touch move events, if there are any + // (DispatchTouchMoveEvents will check the mHavePendingTouchMoves flag and + // bail out if there's nothing to be done). + NotifyVsync(TimeStamp::Now()); + DispatchTouchEvent(aInput); + + { // scope lock + MutexAutoLock lock(mTouchQueueLock); + mInflightNonMoveEvents--; + MOZ_ASSERT(mInflightNonMoveEvents >= 0); + } +} + +void +GeckoTouchDispatcher::DispatchTouchMoveEvents(TimeStamp aVsyncTime) +{ + MultiTouchInput touchMove; + + { + MutexAutoLock lock(mTouchQueueLock); + if (!mHavePendingTouchMoves) { + return; + } + mHavePendingTouchMoves = false; + + int touchCount = mTouchMoveEvents.size(); + TimeDuration vsyncTouchDiff = aVsyncTime - mTouchMoveEvents.back().mTimeStamp; + // The delay threshold is a positive pref, but we're testing to see if the + // vsync time is delayed from the touch, so add a negative sign. + bool isDelayedVsyncEvent = vsyncTouchDiff < -mDelayedVsyncThreshold; + bool isOldTouch = vsyncTouchDiff > mOldTouchThreshold; + bool resample = (touchCount > 1) && !isDelayedVsyncEvent && !isOldTouch; + + if (!resample) { + touchMove = mTouchMoveEvents.back(); + mTouchMoveEvents.clear(); + if (!isDelayedVsyncEvent && !isOldTouch) { + mTouchMoveEvents.push_back(touchMove); + } + } else { + ResampleTouchMoves(touchMove, aVsyncTime); + } + } + + DispatchTouchEvent(touchMove); +} + +static int +Interpolate(int start, int end, TimeDuration aFrameDiff, TimeDuration aTouchDiff) +{ + return start + (((end - start) * aFrameDiff.ToMicroseconds()) / aTouchDiff.ToMicroseconds()); +} + +static const SingleTouchData& +GetTouchByID(const SingleTouchData& aCurrentTouch, MultiTouchInput& aOtherTouch) +{ + int32_t index = aOtherTouch.IndexOfTouch(aCurrentTouch.mIdentifier); + if (index < 0) { + // We can have situations where a previous touch event had 2 fingers + // and we lift 1 finger off. In those cases, we won't find the touch event + // with given id, so just return the current touch, which will be resampled + // without modification and dispatched. + return aCurrentTouch; + } + return aOtherTouch.mTouches[index]; +} + + +// aTouchDiff is the duration between the base and current touch times +// aFrameDiff is the duration between the base and the time we're resampling to +static void +ResampleTouch(MultiTouchInput& aOutTouch, + MultiTouchInput& aBase, MultiTouchInput& aCurrent, + TimeDuration aFrameDiff, TimeDuration aTouchDiff) +{ + aOutTouch = aCurrent; + + // Make sure we only resample the correct finger. + for (size_t i = 0; i < aOutTouch.mTouches.Length(); i++) { + const SingleTouchData& current = aCurrent.mTouches[i]; + const SingleTouchData& base = GetTouchByID(current, aBase); + + const ScreenIntPoint& baseTouchPoint = base.mScreenPoint; + const ScreenIntPoint& currentTouchPoint = current.mScreenPoint; + + ScreenIntPoint newSamplePoint; + newSamplePoint.x = Interpolate(baseTouchPoint.x, currentTouchPoint.x, aFrameDiff, aTouchDiff); + newSamplePoint.y = Interpolate(baseTouchPoint.y, currentTouchPoint.y, aFrameDiff, aTouchDiff); + + aOutTouch.mTouches[i].mScreenPoint = newSamplePoint; + +#ifdef LOG_RESAMPLE_DATA + const char* type = "extrapolate"; + if (aFrameDiff < aTouchDiff) { + type = "interpolate"; + } + + float alpha = aFrameDiff / aTouchDiff; + LOG("%s base (%d, %d), current (%d, %d) to (%d, %d) alpha %f, touch diff %d, frame diff %d\n", + type, + baseTouchPoint.x, baseTouchPoint.y, + currentTouchPoint.x, currentTouchPoint.y, + newSamplePoint.x, newSamplePoint.y, + alpha, (int)aTouchDiff.ToMilliseconds(), (int)aFrameDiff.ToMilliseconds()); +#endif + } +} + +/* + * +> Base touch (The touch before current touch) + * | + * | +> Current touch (Latest touch) + * | | + * | | +> Maximum resample time + * | | | + * +-----+------+--------------------> Time + * ^ ^ + * | | + * +------+--> Potential vsync events which the touches are resampled to + * | | + * | +> Extrapolation + * | + * +> Interpolation + */ + +void +GeckoTouchDispatcher::ResampleTouchMoves(MultiTouchInput& aOutTouch, TimeStamp aVsyncTime) +{ + MOZ_RELEASE_ASSERT(mTouchMoveEvents.size() >= 2); + mTouchQueueLock.AssertCurrentThreadOwns(); + + MultiTouchInput currentTouch = mTouchMoveEvents.back(); + mTouchMoveEvents.pop_back(); + MultiTouchInput baseTouch = mTouchMoveEvents.back(); + mTouchMoveEvents.clear(); + mTouchMoveEvents.push_back(currentTouch); + + TimeStamp sampleTime = aVsyncTime - mVsyncAdjust; + TimeDuration touchDiff = currentTouch.mTimeStamp - baseTouch.mTimeStamp; + + if (touchDiff < mMinDelta) { + aOutTouch = currentTouch; + #ifdef LOG_RESAMPLE_DATA + LOG("The touches are too close, skip resampling\n"); + #endif + return; + } + + if (currentTouch.mTimeStamp < sampleTime) { + TimeDuration maxResampleTime = std::min(touchDiff / int64_t(2), mMaxPredict); + TimeStamp maxTimestamp = currentTouch.mTimeStamp + maxResampleTime; + if (sampleTime > maxTimestamp) { + sampleTime = maxTimestamp; + #ifdef LOG_RESAMPLE_DATA + LOG("Overshot extrapolation time, adjusting sample time\n"); + #endif + } + } + + ResampleTouch(aOutTouch, baseTouch, currentTouch, sampleTime - baseTouch.mTimeStamp, touchDiff); + + // Both mTimeStamp and mTime are being updated to sampleTime here. + // mTime needs to be updated using a delta since TimeStamp doesn't + // provide a way to obtain a raw value. + aOutTouch.mTime += (sampleTime - aOutTouch.mTimeStamp).ToMilliseconds(); + aOutTouch.mTimeStamp = sampleTime; +} + +static bool +IsExpired(const MultiTouchInput& aTouch) +{ + // No pending events, the filter state can be updated. + uint64_t timeNowMs = systemTime(SYSTEM_TIME_MONOTONIC) / 1000000; + return (timeNowMs - aTouch.mTime) > kInputExpirationThresholdMs; +} +void +GeckoTouchDispatcher::DispatchTouchEvent(MultiTouchInput aMultiTouch) +{ + if ((aMultiTouch.mType == MultiTouchInput::MULTITOUCH_END || + aMultiTouch.mType == MultiTouchInput::MULTITOUCH_CANCEL) && + aMultiTouch.mTouches.Length() == 1) { + MutexAutoLock lock(mTouchQueueLock); + mTouchMoveEvents.clear(); + } else if (aMultiTouch.mType == MultiTouchInput::MULTITOUCH_START && + aMultiTouch.mTouches.Length() == 1) { + mTouchEventsFiltered = IsExpired(aMultiTouch); + } + + if (mTouchEventsFiltered) { + return; + } + + nsWindow::DispatchTouchInput(aMultiTouch); + + if (mEnabledUniformityInfo && profiler_is_active()) { + const char* touchAction = "Invalid"; + switch (aMultiTouch.mType) { + case MultiTouchInput::MULTITOUCH_START: + touchAction = "Touch_Event_Down"; + break; + case MultiTouchInput::MULTITOUCH_MOVE: + touchAction = "Touch_Event_Move"; + break; + case MultiTouchInput::MULTITOUCH_END: + case MultiTouchInput::MULTITOUCH_CANCEL: + touchAction = "Touch_Event_Up"; + break; + case MultiTouchInput::MULTITOUCH_SENTINEL: + MOZ_ASSERT_UNREACHABLE("Invalid MultTouchInput."); + break; + } + + const ScreenIntPoint& touchPoint = aMultiTouch.mTouches[0].mScreenPoint; + TouchDataPayload* payload = new TouchDataPayload(touchPoint); + PROFILER_MARKER_PAYLOAD(touchAction, payload); + } +} + +} // namespace mozilla |