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"
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"
35 #include "nsThreadUtils.h"
37 #include <sys/types.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
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
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
);
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
93 GeckoTouchDispatcher::NotifyVsync(TimeStamp aVsyncTimestamp
)
95 if ((sTouchDispatcher
== nullptr) || !gfxPrefs::TouchResampling()) {
99 MOZ_ASSERT(sTouchDispatcher
->mResamplingEnabled
);
100 layers::APZThreadUtils::AssertOnControllerThread();
101 sTouchDispatcher
->DispatchTouchMoveEvents(aVsyncTimestamp
);
104 // Touch data timestamps are in milliseconds, aEventTime is in nanoseconds
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
));
124 mTouchMoveEvents
.push_back(aTouch
);
125 mHavePendingTouchMoves
= true;
126 if (mResamplingEnabled
) {
130 layers::APZThreadUtils::RunOnControllerThread(NewRunnableMethod(
131 this, &GeckoTouchDispatcher::DispatchTouchMoveEvents
, TimeStamp::Now()));
134 MutexAutoLock
lock(mTouchQueueLock
);
135 mInflightNonMoveEvents
++;
137 layers::APZThreadUtils::RunOnControllerThread(NewRunnableMethod(
138 this, &GeckoTouchDispatcher::DispatchTouchNonMoveEvent
, aTouch
));
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
);
156 MutexAutoLock
lock(mTouchQueueLock
);
157 mInflightNonMoveEvents
--;
158 MOZ_ASSERT(mInflightNonMoveEvents
>= 0);
163 GeckoTouchDispatcher::DispatchTouchMoveEvents(TimeStamp aVsyncTime
)
165 MultiTouchInput touchMove
;
168 MutexAutoLock
lock(mTouchQueueLock
);
169 if (!mHavePendingTouchMoves
) {
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
;
184 touchMove
= mTouchMoveEvents
.back();
185 mTouchMoveEvents
.clear();
186 if (!isDelayedVsyncEvent
&& !isOldTouch
) {
187 mTouchMoveEvents
.push_back(touchMove
);
190 ResampleTouchMoves(touchMove
, aVsyncTime
);
193 touchMove
= mTouchMoveEvents
.back();
194 mTouchMoveEvents
.clear();
198 DispatchTouchEvent(touchMove
);
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
);
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
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",
254 baseTouchPoint
.x
, baseTouchPoint
.y
,
255 currentTouchPoint
.x
, currentTouchPoint
.y
,
256 newSamplePoint
.x
, newSamplePoint
.y
,
257 alpha
, (int)aTouchDiff
.ToMilliseconds(), (int)aFrameDiff
.ToMilliseconds());
263 * +> Base touch (The touch before current touch)
265 * | +> Current touch (Latest touch)
267 * | | +> Maximum resample time
269 * +-----+------+--------------------> Time
272 * +------+--> Potential vsync events which the touches are resampled to
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");
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
;
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
;
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
) {
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";
346 case MultiTouchInput::MULTITOUCH_MOVE
:
347 touchAction
= "Touch_Event_Move";
349 case MultiTouchInput::MULTITOUCH_END
:
350 case MultiTouchInput::MULTITOUCH_CANCEL
:
351 touchAction
= "Touch_Event_Up";
355 const ScreenIntPoint
& touchPoint
= aMultiTouch
.mTouches
[0].mScreenPoint
;
356 TouchDataPayload
* payload
= new TouchDataPayload(touchPoint
);
357 PROFILER_MARKER_PAYLOAD(touchAction
, payload
);
361 } // namespace mozilla