Bumping manifests a=b2g-bump
[gecko.git] / widget / gonk / GeckoTouchDispatcher.cpp
blobc267cb94f7ced9f8bd43c3feade612504aae3919
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 sts=2 et sw=2 tw=80: */
3 /* Copyright 2014 Mozilla Foundation and Mozilla contributors
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
18 #include "FrameMetrics.h"
19 #include "GeckoProfiler.h"
20 #include "GeckoTouchDispatcher.h"
21 #include "InputData.h"
22 #include "ProfilerMarkers.h"
23 #include "base/basictypes.h"
24 #include "gfxPrefs.h"
25 #include "libui/Input.h"
26 #include "mozilla/ClearOnShutdown.h"
27 #include "mozilla/Mutex.h"
28 #include "mozilla/TimeStamp.h"
29 #include "mozilla/TouchEvents.h"
30 #include "mozilla/dom/Touch.h"
31 #include "mozilla/layers/APZThreadUtils.h"
32 #include "mozilla/layers/CompositorParent.h"
33 #include "nsAppShell.h"
34 #include "nsDebug.h"
35 #include "nsThreadUtils.h"
36 #include "nsWindow.h"
37 #include <sys/types.h>
38 #include <unistd.h>
39 #include <utils/Timers.h>
41 #define LOG(args...) \
42 __android_log_print(ANDROID_LOG_INFO, "Gonk" , ## args)
44 // uncomment to print log resample data
45 // #define LOG_RESAMPLE_DATA 1
47 namespace mozilla {
49 // Amount of time in MS before an input is considered expired.
50 static const uint64_t kInputExpirationThresholdMs = 1000;
52 static StaticRefPtr<GeckoTouchDispatcher> sTouchDispatcher;
54 GeckoTouchDispatcher::GeckoTouchDispatcher()
55 : mTouchQueueLock("GeckoTouchDispatcher::mTouchQueueLock")
56 , mHavePendingTouchMoves(false)
57 , mInflightNonMoveEvents(0)
58 , mTouchEventsFiltered(false)
60 // Since GeckoTouchDispatcher is initialized when input is initialized
61 // and reads gfxPrefs, it is the first thing to touch gfxPrefs.
62 // The first thing to touch gfxPrefs MUST occur on the main thread and init
63 // the singleton
64 MOZ_ASSERT(sTouchDispatcher == nullptr);
65 MOZ_ASSERT(NS_IsMainThread());
66 gfxPrefs::GetSingleton();
68 mEnabledUniformityInfo = gfxPrefs::UniformityInfo();
69 mResamplingEnabled = gfxPrefs::TouchResampling() &&
70 gfxPrefs::HardwareVsyncEnabled();
71 mVsyncAdjust = TimeDuration::FromMilliseconds(gfxPrefs::TouchVsyncSampleAdjust());
72 mMaxPredict = TimeDuration::FromMilliseconds(gfxPrefs::TouchResampleMaxPredict());
73 mOldTouchThreshold = TimeDuration::FromMilliseconds(gfxPrefs::TouchResampleOldTouchThreshold());
74 mDelayedVsyncThreshold = TimeDuration::FromMilliseconds(gfxPrefs::TouchResampleVsyncDelayThreshold());
75 sTouchDispatcher = this;
76 ClearOnShutdown(&sTouchDispatcher);
79 /* static */ void
80 GeckoTouchDispatcher::SetCompositorVsyncObserver(mozilla::layers::CompositorVsyncObserver *aObserver)
82 MOZ_ASSERT(sTouchDispatcher != nullptr);
83 MOZ_ASSERT(NS_IsMainThread());
84 // We assume on b2g that there is only 1 CompositorParent
85 MOZ_ASSERT(sTouchDispatcher->mCompositorVsyncObserver == nullptr);
86 if (gfxPrefs::TouchResampling()) {
87 sTouchDispatcher->mCompositorVsyncObserver = aObserver;
91 // Timestamp is in nanoseconds
92 /* static */ void
93 GeckoTouchDispatcher::NotifyVsync(TimeStamp aVsyncTimestamp)
95 if ((sTouchDispatcher == nullptr) || !gfxPrefs::TouchResampling()) {
96 return;
99 MOZ_ASSERT(sTouchDispatcher->mResamplingEnabled);
100 layers::APZThreadUtils::AssertOnControllerThread();
101 sTouchDispatcher->DispatchTouchMoveEvents(aVsyncTimestamp);
104 // Touch data timestamps are in milliseconds, aEventTime is in nanoseconds
105 void
106 GeckoTouchDispatcher::NotifyTouch(MultiTouchInput& aTouch, TimeStamp aEventTime)
108 if (mCompositorVsyncObserver) {
109 mCompositorVsyncObserver->SetNeedsComposite(true);
112 if (aTouch.mType == MultiTouchInput::MULTITOUCH_MOVE) {
113 MutexAutoLock lock(mTouchQueueLock);
114 if (mInflightNonMoveEvents > 0) {
115 // If we have any pending non-move events, we shouldn't resample the
116 // move events because we might end up dispatching events out of order.
117 // Instead, fall back to a non-resampling in-order dispatch until we're
118 // done processing the non-move events.
119 layers::APZThreadUtils::RunOnControllerThread(NewRunnableMethod(
120 this, &GeckoTouchDispatcher::DispatchTouchEvent, aTouch));
121 return;
124 mTouchMoveEvents.push_back(aTouch);
125 mHavePendingTouchMoves = true;
126 if (mResamplingEnabled) {
127 return;
130 layers::APZThreadUtils::RunOnControllerThread(NewRunnableMethod(
131 this, &GeckoTouchDispatcher::DispatchTouchMoveEvents, TimeStamp::Now()));
132 } else {
133 { // scope lock
134 MutexAutoLock lock(mTouchQueueLock);
135 mInflightNonMoveEvents++;
137 layers::APZThreadUtils::RunOnControllerThread(NewRunnableMethod(
138 this, &GeckoTouchDispatcher::DispatchTouchNonMoveEvent, aTouch));
142 void
143 GeckoTouchDispatcher::DispatchTouchNonMoveEvent(MultiTouchInput aInput)
145 layers::APZThreadUtils::AssertOnControllerThread();
147 if (mResamplingEnabled) {
148 // Flush pending touch move events, if there are any
149 // (DispatchTouchMoveEvents will check the mHavePendingTouchMoves flag and
150 // bail out if there's nothing to be done).
151 NotifyVsync(TimeStamp::Now());
153 DispatchTouchEvent(aInput);
155 { // scope lock
156 MutexAutoLock lock(mTouchQueueLock);
157 mInflightNonMoveEvents--;
158 MOZ_ASSERT(mInflightNonMoveEvents >= 0);
162 void
163 GeckoTouchDispatcher::DispatchTouchMoveEvents(TimeStamp aVsyncTime)
165 MultiTouchInput touchMove;
168 MutexAutoLock lock(mTouchQueueLock);
169 if (!mHavePendingTouchMoves) {
170 return;
172 mHavePendingTouchMoves = false;
174 if (mResamplingEnabled) {
175 int touchCount = mTouchMoveEvents.size();
176 TimeDuration vsyncTouchDiff = aVsyncTime - mTouchMoveEvents.back().mTimeStamp;
177 // The delay threshold is a positive pref, but we're testing to see if the
178 // vsync time is delayed from the touch, so add a negative sign.
179 bool isDelayedVsyncEvent = vsyncTouchDiff < -mDelayedVsyncThreshold;
180 bool isOldTouch = vsyncTouchDiff > mOldTouchThreshold;
181 bool resample = (touchCount > 1) && !isDelayedVsyncEvent && !isOldTouch;
183 if (!resample) {
184 touchMove = mTouchMoveEvents.back();
185 mTouchMoveEvents.clear();
186 if (!isDelayedVsyncEvent && !isOldTouch) {
187 mTouchMoveEvents.push_back(touchMove);
189 } else {
190 ResampleTouchMoves(touchMove, aVsyncTime);
192 } else {
193 touchMove = mTouchMoveEvents.back();
194 mTouchMoveEvents.clear();
198 DispatchTouchEvent(touchMove);
201 static int
202 Interpolate(int start, int end, TimeDuration aFrameDiff, TimeDuration aTouchDiff)
204 return start + (((end - start) * aFrameDiff.ToMicroseconds()) / aTouchDiff.ToMicroseconds());
207 static const SingleTouchData&
208 GetTouchByID(const SingleTouchData& aCurrentTouch, MultiTouchInput& aOtherTouch)
210 int32_t index = aOtherTouch.IndexOfTouch(aCurrentTouch.mIdentifier);
211 if (index < 0) {
212 // We can have situations where a previous touch event had 2 fingers
213 // and we lift 1 finger off. In those cases, we won't find the touch event
214 // with given id, so just return the current touch, which will be resampled
215 // without modification and dispatched.
216 return aCurrentTouch;
218 return aOtherTouch.mTouches[index];
222 // aTouchDiff is the duration between the base and current touch times
223 // aFrameDiff is the duration between the base and the time we're resampling to
224 static void
225 ResampleTouch(MultiTouchInput& aOutTouch,
226 MultiTouchInput& aBase, MultiTouchInput& aCurrent,
227 TimeDuration aFrameDiff, TimeDuration aTouchDiff)
229 aOutTouch = aCurrent;
231 // Make sure we only resample the correct finger.
232 for (size_t i = 0; i < aOutTouch.mTouches.Length(); i++) {
233 const SingleTouchData& current = aCurrent.mTouches[i];
234 const SingleTouchData& base = GetTouchByID(current, aBase);
236 const ScreenIntPoint& baseTouchPoint = base.mScreenPoint;
237 const ScreenIntPoint& currentTouchPoint = current.mScreenPoint;
239 ScreenIntPoint newSamplePoint;
240 newSamplePoint.x = Interpolate(baseTouchPoint.x, currentTouchPoint.x, aFrameDiff, aTouchDiff);
241 newSamplePoint.y = Interpolate(baseTouchPoint.y, currentTouchPoint.y, aFrameDiff, aTouchDiff);
243 aOutTouch.mTouches[i].mScreenPoint = newSamplePoint;
245 #ifdef LOG_RESAMPLE_DATA
246 const char* type = "extrapolate";
247 if (aFrameDiff < aTouchDiff) {
248 type = "interpolate";
251 float alpha = aFrameDiff / aTouchDiff;
252 LOG("%s base (%d, %d), current (%d, %d) to (%d, %d) alpha %f, touch diff %d, frame diff %d\n",
253 type,
254 baseTouchPoint.x, baseTouchPoint.y,
255 currentTouchPoint.x, currentTouchPoint.y,
256 newSamplePoint.x, newSamplePoint.y,
257 alpha, (int)aTouchDiff.ToMilliseconds(), (int)aFrameDiff.ToMilliseconds());
258 #endif
263 * +> Base touch (The touch before current touch)
265 * | +> Current touch (Latest touch)
266 * | |
267 * | | +> Maximum resample time
268 * | | |
269 * +-----+------+--------------------> Time
270 * ^ ^
271 * | |
272 * +------+--> Potential vsync events which the touches are resampled to
273 * | |
274 * | +> Extrapolation
276 * +> Interpolation
279 void
280 GeckoTouchDispatcher::ResampleTouchMoves(MultiTouchInput& aOutTouch, TimeStamp aVsyncTime)
282 MOZ_RELEASE_ASSERT(mTouchMoveEvents.size() >= 2);
283 mTouchQueueLock.AssertCurrentThreadOwns();
285 MultiTouchInput currentTouch = mTouchMoveEvents.back();
286 mTouchMoveEvents.pop_back();
287 MultiTouchInput baseTouch = mTouchMoveEvents.back();
288 mTouchMoveEvents.clear();
289 mTouchMoveEvents.push_back(currentTouch);
291 TimeStamp sampleTime = aVsyncTime - mVsyncAdjust;
292 TimeDuration touchDiff = currentTouch.mTimeStamp - baseTouch.mTimeStamp;
294 if (currentTouch.mTimeStamp < sampleTime) {
295 TimeDuration maxResampleTime = std::min(touchDiff / 2, mMaxPredict);
296 TimeStamp maxTimestamp = currentTouch.mTimeStamp + maxResampleTime;
297 if (sampleTime > maxTimestamp) {
298 sampleTime = maxTimestamp;
299 #ifdef LOG_RESAMPLE_DATA
300 LOG("Overshot extrapolation time, adjusting sample time\n");
301 #endif
305 ResampleTouch(aOutTouch, baseTouch, currentTouch, sampleTime - baseTouch.mTimeStamp, touchDiff);
307 // Both mTimeStamp and mTime are being updated to sampleTime here.
308 // mTime needs to be updated using a delta since TimeStamp doesn't
309 // provide a way to obtain a raw value.
310 aOutTouch.mTime += (sampleTime - aOutTouch.mTimeStamp).ToMilliseconds();
311 aOutTouch.mTimeStamp = sampleTime;
314 static bool
315 IsExpired(const MultiTouchInput& aTouch)
317 // No pending events, the filter state can be updated.
318 uint64_t timeNowMs = systemTime(SYSTEM_TIME_MONOTONIC) / 1000000;
319 return (timeNowMs - aTouch.mTime) > kInputExpirationThresholdMs;
321 void
322 GeckoTouchDispatcher::DispatchTouchEvent(MultiTouchInput aMultiTouch)
324 if ((aMultiTouch.mType == MultiTouchInput::MULTITOUCH_END ||
325 aMultiTouch.mType == MultiTouchInput::MULTITOUCH_CANCEL) &&
326 aMultiTouch.mTouches.Length() == 1) {
327 MutexAutoLock lock(mTouchQueueLock);
328 mTouchMoveEvents.clear();
329 } else if (aMultiTouch.mType == MultiTouchInput::MULTITOUCH_START &&
330 aMultiTouch.mTouches.Length() == 1) {
331 mTouchEventsFiltered = IsExpired(aMultiTouch);
334 if (mTouchEventsFiltered) {
335 return;
338 nsWindow::DispatchTouchInput(aMultiTouch);
340 if (mEnabledUniformityInfo && profiler_is_active()) {
341 const char* touchAction = "Invalid";
342 switch (aMultiTouch.mType) {
343 case MultiTouchInput::MULTITOUCH_START:
344 touchAction = "Touch_Event_Down";
345 break;
346 case MultiTouchInput::MULTITOUCH_MOVE:
347 touchAction = "Touch_Event_Move";
348 break;
349 case MultiTouchInput::MULTITOUCH_END:
350 case MultiTouchInput::MULTITOUCH_CANCEL:
351 touchAction = "Touch_Event_Up";
352 break;
355 const ScreenIntPoint& touchPoint = aMultiTouch.mTouches[0].mScreenPoint;
356 TouchDataPayload* payload = new TouchDataPayload(touchPoint);
357 PROFILER_MARKER_PAYLOAD(touchAction, payload);
361 } // namespace mozilla