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