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
4  *
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
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
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.
16  */
17 
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/CompositorBridgeParent.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>
40 
41 #undef LOG
42 #define LOG(args...)                                            \
43   __android_log_print(ANDROID_LOG_INFO, "Gonk" , ## args)
44 
45 // uncomment to print log resample data
46 // #define LOG_RESAMPLE_DATA 1
47 
48 namespace mozilla {
49 
50 // Amount of time in MS before an input is considered expired.
51 static const uint64_t kInputExpirationThresholdMs = 1000;
52 
53 static StaticRefPtr<GeckoTouchDispatcher> sTouchDispatcher;
54 
55 /* static */ GeckoTouchDispatcher*
GetInstance()56 GeckoTouchDispatcher::GetInstance()
57 {
58   if (!sTouchDispatcher) {
59     sTouchDispatcher = new GeckoTouchDispatcher();
60     ClearOnShutdown(&sTouchDispatcher);
61   }
62   return sTouchDispatcher;
63 }
64 
GeckoTouchDispatcher()65 GeckoTouchDispatcher::GeckoTouchDispatcher()
66   : mTouchQueueLock("GeckoTouchDispatcher::mTouchQueueLock")
67   , mHavePendingTouchMoves(false)
68   , mInflightNonMoveEvents(0)
69   , mTouchEventsFiltered(false)
70 {
71   // Since GeckoTouchDispatcher is initialized when input is initialized
72   // and reads gfxPrefs, it is the first thing to touch gfxPrefs.
73   // The first thing to touch gfxPrefs MUST occur on the main thread and init
74   // the singleton
75   MOZ_ASSERT(sTouchDispatcher == nullptr);
76   MOZ_ASSERT(NS_IsMainThread());
77   gfxPrefs::GetSingleton();
78 
79   mEnabledUniformityInfo = gfxPrefs::UniformityInfo();
80   mVsyncAdjust = TimeDuration::FromMilliseconds(gfxPrefs::TouchVsyncSampleAdjust());
81   mMaxPredict = TimeDuration::FromMilliseconds(gfxPrefs::TouchResampleMaxPredict());
82   mMinDelta = TimeDuration::FromMilliseconds(gfxPrefs::TouchResampleMinDelta());
83   mOldTouchThreshold = TimeDuration::FromMilliseconds(gfxPrefs::TouchResampleOldTouchThreshold());
84   mDelayedVsyncThreshold = TimeDuration::FromMilliseconds(gfxPrefs::TouchResampleVsyncDelayThreshold());
85 }
86 
87 void
SetCompositorVsyncScheduler(mozilla::layers::CompositorVsyncScheduler * aObserver)88 GeckoTouchDispatcher::SetCompositorVsyncScheduler(mozilla::layers::CompositorVsyncScheduler *aObserver)
89 {
90   MOZ_ASSERT(NS_IsMainThread());
91   // We assume on b2g that there is only 1 CompositorBridgeParent
92   MOZ_ASSERT(mCompositorVsyncScheduler == nullptr);
93   mCompositorVsyncScheduler = aObserver;
94 }
95 
96 void
NotifyVsync(TimeStamp aVsyncTimestamp)97 GeckoTouchDispatcher::NotifyVsync(TimeStamp aVsyncTimestamp)
98 {
99   layers::APZThreadUtils::AssertOnControllerThread();
100   DispatchTouchMoveEvents(aVsyncTimestamp);
101 }
102 
103 // Touch data timestamps are in milliseconds, aEventTime is in nanoseconds
104 void
NotifyTouch(MultiTouchInput & aTouch,TimeStamp aEventTime)105 GeckoTouchDispatcher::NotifyTouch(MultiTouchInput& aTouch, TimeStamp aEventTime)
106 {
107   if (mCompositorVsyncScheduler) {
108     mCompositorVsyncScheduler->SetNeedsComposite();
109   }
110 
111   if (aTouch.mType == MultiTouchInput::MULTITOUCH_MOVE) {
112     MutexAutoLock lock(mTouchQueueLock);
113     if (mInflightNonMoveEvents > 0) {
114       // If we have any pending non-move events, we shouldn't resample the
115       // move events because we might end up dispatching events out of order.
116       // Instead, fall back to a non-resampling in-order dispatch until we're
117       // done processing the non-move events.
118       layers::APZThreadUtils::RunOnControllerThread(NewRunnableMethod<MultiTouchInput>(
119         this, &GeckoTouchDispatcher::DispatchTouchEvent, aTouch));
120       return;
121     }
122 
123     mTouchMoveEvents.push_back(aTouch);
124     mHavePendingTouchMoves = true;
125   } else {
126     { // scope lock
127       MutexAutoLock lock(mTouchQueueLock);
128       mInflightNonMoveEvents++;
129     }
130     layers::APZThreadUtils::RunOnControllerThread(NewRunnableMethod<MultiTouchInput>(
131       this, &GeckoTouchDispatcher::DispatchTouchNonMoveEvent, aTouch));
132   }
133 }
134 
135 void
DispatchTouchNonMoveEvent(MultiTouchInput aInput)136 GeckoTouchDispatcher::DispatchTouchNonMoveEvent(MultiTouchInput aInput)
137 {
138   layers::APZThreadUtils::AssertOnControllerThread();
139 
140   // Flush pending touch move events, if there are any
141   // (DispatchTouchMoveEvents will check the mHavePendingTouchMoves flag and
142   // bail out if there's nothing to be done).
143   NotifyVsync(TimeStamp::Now());
144   DispatchTouchEvent(aInput);
145 
146   { // scope lock
147     MutexAutoLock lock(mTouchQueueLock);
148     mInflightNonMoveEvents--;
149     MOZ_ASSERT(mInflightNonMoveEvents >= 0);
150   }
151 }
152 
153 void
DispatchTouchMoveEvents(TimeStamp aVsyncTime)154 GeckoTouchDispatcher::DispatchTouchMoveEvents(TimeStamp aVsyncTime)
155 {
156   MultiTouchInput touchMove;
157 
158   {
159     MutexAutoLock lock(mTouchQueueLock);
160     if (!mHavePendingTouchMoves) {
161       return;
162     }
163     mHavePendingTouchMoves = false;
164 
165     int touchCount = mTouchMoveEvents.size();
166     TimeDuration vsyncTouchDiff = aVsyncTime - mTouchMoveEvents.back().mTimeStamp;
167     // The delay threshold is a positive pref, but we're testing to see if the
168     // vsync time is delayed from the touch, so add a negative sign.
169     bool isDelayedVsyncEvent = vsyncTouchDiff < -mDelayedVsyncThreshold;
170     bool isOldTouch = vsyncTouchDiff > mOldTouchThreshold;
171     bool resample = (touchCount > 1) && !isDelayedVsyncEvent && !isOldTouch;
172 
173     if (!resample) {
174       touchMove = mTouchMoveEvents.back();
175       mTouchMoveEvents.clear();
176       if (!isDelayedVsyncEvent && !isOldTouch) {
177         mTouchMoveEvents.push_back(touchMove);
178       }
179     } else {
180       ResampleTouchMoves(touchMove, aVsyncTime);
181     }
182   }
183 
184   DispatchTouchEvent(touchMove);
185 }
186 
187 static int
Interpolate(int start,int end,TimeDuration aFrameDiff,TimeDuration aTouchDiff)188 Interpolate(int start, int end, TimeDuration aFrameDiff, TimeDuration aTouchDiff)
189 {
190   return start + (((end - start) * aFrameDiff.ToMicroseconds()) / aTouchDiff.ToMicroseconds());
191 }
192 
193 static const SingleTouchData&
GetTouchByID(const SingleTouchData & aCurrentTouch,MultiTouchInput & aOtherTouch)194 GetTouchByID(const SingleTouchData& aCurrentTouch, MultiTouchInput& aOtherTouch)
195 {
196   int32_t index = aOtherTouch.IndexOfTouch(aCurrentTouch.mIdentifier);
197   if (index < 0) {
198     // We can have situations where a previous touch event had 2 fingers
199     // and we lift 1 finger off. In those cases, we won't find the touch event
200     // with given id, so just return the current touch, which will be resampled
201     // without modification and dispatched.
202     return aCurrentTouch;
203   }
204   return aOtherTouch.mTouches[index];
205 }
206 
207 
208 // aTouchDiff is the duration between the base and current touch times
209 // aFrameDiff is the duration between the base and the time we're resampling to
210 static void
ResampleTouch(MultiTouchInput & aOutTouch,MultiTouchInput & aBase,MultiTouchInput & aCurrent,TimeDuration aFrameDiff,TimeDuration aTouchDiff)211 ResampleTouch(MultiTouchInput& aOutTouch,
212               MultiTouchInput& aBase, MultiTouchInput& aCurrent,
213               TimeDuration aFrameDiff, TimeDuration aTouchDiff)
214 {
215   aOutTouch = aCurrent;
216 
217   // Make sure we only resample the correct finger.
218   for (size_t i = 0; i < aOutTouch.mTouches.Length(); i++) {
219     const SingleTouchData& current = aCurrent.mTouches[i];
220     const SingleTouchData& base = GetTouchByID(current, aBase);
221 
222     const ScreenIntPoint& baseTouchPoint = base.mScreenPoint;
223     const ScreenIntPoint& currentTouchPoint = current.mScreenPoint;
224 
225     ScreenIntPoint newSamplePoint;
226     newSamplePoint.x = Interpolate(baseTouchPoint.x, currentTouchPoint.x, aFrameDiff, aTouchDiff);
227     newSamplePoint.y = Interpolate(baseTouchPoint.y, currentTouchPoint.y, aFrameDiff, aTouchDiff);
228 
229     aOutTouch.mTouches[i].mScreenPoint = newSamplePoint;
230 
231 #ifdef LOG_RESAMPLE_DATA
232     const char* type = "extrapolate";
233     if (aFrameDiff < aTouchDiff) {
234       type = "interpolate";
235     }
236 
237     float alpha = aFrameDiff / aTouchDiff;
238     LOG("%s base (%d, %d), current (%d, %d) to (%d, %d) alpha %f, touch diff %d, frame diff %d\n",
239         type,
240         baseTouchPoint.x, baseTouchPoint.y,
241         currentTouchPoint.x, currentTouchPoint.y,
242         newSamplePoint.x, newSamplePoint.y,
243         alpha, (int)aTouchDiff.ToMilliseconds(), (int)aFrameDiff.ToMilliseconds());
244 #endif
245   }
246 }
247 
248 /*
249  * +> Base touch (The touch before current touch)
250  * |
251  * |     +> Current touch (Latest touch)
252  * |     |
253  * |     |      +> Maximum resample time
254  * |     |      |
255  * +-----+------+--------------------> Time
256  *    ^      ^
257  *    |      |
258  *    +------+--> Potential vsync events which the touches are resampled to
259  *    |      |
260  *    |      +> Extrapolation
261  *    |
262  *    +> Interpolation
263  */
264 
265 void
ResampleTouchMoves(MultiTouchInput & aOutTouch,TimeStamp aVsyncTime)266 GeckoTouchDispatcher::ResampleTouchMoves(MultiTouchInput& aOutTouch, TimeStamp aVsyncTime)
267 {
268   MOZ_RELEASE_ASSERT(mTouchMoveEvents.size() >= 2);
269   mTouchQueueLock.AssertCurrentThreadOwns();
270 
271   MultiTouchInput currentTouch = mTouchMoveEvents.back();
272   mTouchMoveEvents.pop_back();
273   MultiTouchInput baseTouch = mTouchMoveEvents.back();
274   mTouchMoveEvents.clear();
275   mTouchMoveEvents.push_back(currentTouch);
276 
277   TimeStamp sampleTime = aVsyncTime - mVsyncAdjust;
278   TimeDuration touchDiff = currentTouch.mTimeStamp - baseTouch.mTimeStamp;
279 
280   if (touchDiff < mMinDelta) {
281     aOutTouch = currentTouch;
282     #ifdef LOG_RESAMPLE_DATA
283     LOG("The touches are too close, skip resampling\n");
284     #endif
285     return;
286   }
287 
288   if (currentTouch.mTimeStamp < sampleTime) {
289     TimeDuration maxResampleTime = std::min(touchDiff / int64_t(2), mMaxPredict);
290     TimeStamp maxTimestamp = currentTouch.mTimeStamp + maxResampleTime;
291     if (sampleTime > maxTimestamp) {
292       sampleTime = maxTimestamp;
293       #ifdef LOG_RESAMPLE_DATA
294       LOG("Overshot extrapolation time, adjusting sample time\n");
295       #endif
296     }
297   }
298 
299   ResampleTouch(aOutTouch, baseTouch, currentTouch, sampleTime - baseTouch.mTimeStamp, touchDiff);
300 
301   // Both mTimeStamp and mTime are being updated to sampleTime here.
302   // mTime needs to be updated using a delta since TimeStamp doesn't
303   // provide a way to obtain a raw value.
304   aOutTouch.mTime += (sampleTime - aOutTouch.mTimeStamp).ToMilliseconds();
305   aOutTouch.mTimeStamp = sampleTime;
306 }
307 
308 static bool
IsExpired(const MultiTouchInput & aTouch)309 IsExpired(const MultiTouchInput& aTouch)
310 {
311   // No pending events, the filter state can be updated.
312   uint64_t timeNowMs = systemTime(SYSTEM_TIME_MONOTONIC) / 1000000;
313   return (timeNowMs - aTouch.mTime) > kInputExpirationThresholdMs;
314 }
315 void
DispatchTouchEvent(MultiTouchInput aMultiTouch)316 GeckoTouchDispatcher::DispatchTouchEvent(MultiTouchInput aMultiTouch)
317 {
318   if ((aMultiTouch.mType == MultiTouchInput::MULTITOUCH_END ||
319        aMultiTouch.mType == MultiTouchInput::MULTITOUCH_CANCEL) &&
320       aMultiTouch.mTouches.Length() == 1) {
321     MutexAutoLock lock(mTouchQueueLock);
322     mTouchMoveEvents.clear();
323   } else if (aMultiTouch.mType == MultiTouchInput::MULTITOUCH_START &&
324              aMultiTouch.mTouches.Length() == 1) {
325     mTouchEventsFiltered = IsExpired(aMultiTouch);
326   }
327 
328   if (mTouchEventsFiltered) {
329     return;
330   }
331 
332   nsWindow::DispatchTouchInput(aMultiTouch);
333 
334   if (mEnabledUniformityInfo && profiler_is_active()) {
335     const char* touchAction = "Invalid";
336     switch (aMultiTouch.mType) {
337       case MultiTouchInput::MULTITOUCH_START:
338         touchAction = "Touch_Event_Down";
339         break;
340       case MultiTouchInput::MULTITOUCH_MOVE:
341         touchAction = "Touch_Event_Move";
342         break;
343       case MultiTouchInput::MULTITOUCH_END:
344       case MultiTouchInput::MULTITOUCH_CANCEL:
345         touchAction = "Touch_Event_Up";
346         break;
347       case MultiTouchInput::MULTITOUCH_SENTINEL:
348         MOZ_ASSERT_UNREACHABLE("Invalid MultTouchInput.");
349         break;
350     }
351 
352     const ScreenIntPoint& touchPoint = aMultiTouch.mTouches[0].mScreenPoint;
353     TouchDataPayload* payload = new TouchDataPayload(touchPoint);
354     PROFILER_MARKER_PAYLOAD(touchAction, payload);
355   }
356 }
357 
358 } // namespace mozilla
359