summaryrefslogtreecommitdiffstats
path: root/widget/gonk/GeckoTouchDispatcher.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'widget/gonk/GeckoTouchDispatcher.cpp')
-rw-r--r--widget/gonk/GeckoTouchDispatcher.cpp358
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