зеркало из https://github.com/mozilla/gecko-dev.git
Bug 970751 Resample touch events. r=mwu,kats. a=kwierso
This commit is contained in:
Родитель
89b11681d6
Коммит
a66b70e1af
|
@ -197,6 +197,11 @@ private:
|
|||
|
||||
// Use vsync events generated by hardware
|
||||
DECL_GFX_PREF(Once, "gfx.frameuniformity.hw-vsync", FrameUniformityHWVsyncEnabled, bool, false);
|
||||
DECL_GFX_PREF(Once, "gfx.touch.resample", TouchResampling, bool, false);
|
||||
// These times should be in nanoseconds
|
||||
DECL_GFX_PREF(Once, "gfx.touch.resample.max-predict", TouchResampleMaxPredict, int32_t, 8000000);
|
||||
DECL_GFX_PREF(Once, "gfx.touch.resample.vsync-adjust", TouchVsyncSampleAdjust, int32_t, 5000000);
|
||||
DECL_GFX_PREF(Once, "gfx.touch.resample.min-resample", TouchResampleMinTime, int32_t, 2000000);
|
||||
|
||||
DECL_GFX_PREF(Live, "gl.msaa-level", MSAALevel, uint32_t, 2);
|
||||
|
||||
|
|
|
@ -0,0 +1,451 @@
|
|||
/* -*- 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 "base/basictypes.h"
|
||||
#include "gfxPrefs.h"
|
||||
#include "libui/Input.h"
|
||||
#include "mozilla/MouseEvents.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/TouchEvents.h"
|
||||
#include "mozilla/dom/Touch.h"
|
||||
#include "nsAppShell.h"
|
||||
#include "nsDebug.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsWindow.h"
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <utils/Timers.h>
|
||||
|
||||
#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 int32_t nanosecToMillisec(int64_t nanosec) { return nanosec / 1000000; }
|
||||
|
||||
static StaticRefPtr<GeckoTouchDispatcher> sTouchDispatcher;
|
||||
|
||||
GeckoTouchDispatcher::GeckoTouchDispatcher()
|
||||
: mTouchQueueLock("GeckoTouchDispatcher::mTouchQueueLock")
|
||||
, mTouchEventsFiltered(false)
|
||||
, mTouchDownCount(0)
|
||||
, mTouchTimeDiff(0)
|
||||
, mLastTouchTime(0)
|
||||
{
|
||||
// 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();
|
||||
mResamplingEnabled = gfxPrefs::TouchResampling() &&
|
||||
gfxPrefs::FrameUniformityHWVsyncEnabled();
|
||||
mVsyncAdjust = gfxPrefs::TouchVsyncSampleAdjust();
|
||||
mMaxPredict = gfxPrefs::TouchResampleMaxPredict();
|
||||
mMinResampleTime = gfxPrefs::TouchResampleMinTime();
|
||||
sTouchDispatcher = this;
|
||||
}
|
||||
|
||||
class DispatchTouchEventsMainThread : public nsRunnable
|
||||
{
|
||||
public:
|
||||
DispatchTouchEventsMainThread(GeckoTouchDispatcher* aTouchDispatcher,
|
||||
uint64_t aVsyncTime)
|
||||
: mTouchDispatcher(aTouchDispatcher)
|
||||
, mVsyncTime(aVsyncTime)
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMETHOD Run()
|
||||
{
|
||||
mTouchDispatcher->DispatchTouchMoveEvents(mVsyncTime);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
nsRefPtr<GeckoTouchDispatcher> mTouchDispatcher;
|
||||
uint64_t mVsyncTime;
|
||||
};
|
||||
|
||||
class DispatchSingleTouchMainThread : public nsRunnable
|
||||
{
|
||||
public:
|
||||
DispatchSingleTouchMainThread(GeckoTouchDispatcher* aTouchDispatcher,
|
||||
MultiTouchInput& aTouch)
|
||||
: mTouchDispatcher(aTouchDispatcher)
|
||||
, mTouch(aTouch)
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMETHOD Run()
|
||||
{
|
||||
mTouchDispatcher->DispatchTouchEvent(mTouch);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
nsRefPtr<GeckoTouchDispatcher> mTouchDispatcher;
|
||||
MultiTouchInput mTouch;
|
||||
};
|
||||
|
||||
// Timestamp is in nanoseconds
|
||||
/* static */ bool
|
||||
GeckoTouchDispatcher::NotifyVsync(uint64_t aVsyncTimestamp)
|
||||
{
|
||||
if (sTouchDispatcher == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(sTouchDispatcher->mResamplingEnabled);
|
||||
bool haveTouchData = false;
|
||||
{
|
||||
MutexAutoLock lock(sTouchDispatcher->mTouchQueueLock);
|
||||
haveTouchData = !sTouchDispatcher->mTouchMoveEvents.empty();
|
||||
}
|
||||
|
||||
if (haveTouchData) {
|
||||
NS_DispatchToMainThread(new DispatchTouchEventsMainThread(sTouchDispatcher, aVsyncTimestamp));
|
||||
}
|
||||
|
||||
return haveTouchData;
|
||||
}
|
||||
|
||||
// Touch data timestamps are in milliseconds, aEventTime is in nanoseconds
|
||||
void
|
||||
GeckoTouchDispatcher::NotifyTouch(MultiTouchInput& aData, uint64_t aEventTime)
|
||||
{
|
||||
if (mResamplingEnabled) {
|
||||
switch (aData.mType) {
|
||||
case MultiTouchInput::MULTITOUCH_MOVE:
|
||||
{
|
||||
MutexAutoLock lock(mTouchQueueLock);
|
||||
mTouchMoveEvents.push_back(aData);
|
||||
mTouchTimeDiff = aEventTime - mLastTouchTime;
|
||||
mLastTouchTime = aEventTime;
|
||||
return;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
NS_DispatchToMainThread(new DispatchSingleTouchMainThread(this, aData));
|
||||
}
|
||||
|
||||
void
|
||||
GeckoTouchDispatcher::DispatchTouchMoveEvents(uint64_t aVsyncTime)
|
||||
{
|
||||
MultiTouchInput touchMove;
|
||||
|
||||
{
|
||||
MutexAutoLock lock(mTouchQueueLock);
|
||||
if (mTouchMoveEvents.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int touchCount = mTouchMoveEvents.size();
|
||||
// Both aVsynctime and mLastTouchTime are uint64_t
|
||||
// Need to store as a signed int.
|
||||
int64_t vsyncTouchDiff = aVsyncTime - mLastTouchTime;
|
||||
bool resample = (touchCount > 1) &&
|
||||
(vsyncTouchDiff > mMinResampleTime);
|
||||
|
||||
if (!resample) {
|
||||
touchMove = mTouchMoveEvents.back();
|
||||
mTouchMoveEvents.clear();
|
||||
mTouchMoveEvents.push_back(touchMove);
|
||||
} else {
|
||||
ResampleTouchMoves(touchMove, aVsyncTime);
|
||||
}
|
||||
}
|
||||
|
||||
DispatchTouchEvent(touchMove);
|
||||
}
|
||||
|
||||
static int
|
||||
Interpolate(int start, int end, int64_t aFrameDiff, int64_t aTouchDiff)
|
||||
{
|
||||
return start + (((end - start) * aFrameDiff) / aTouchDiff);
|
||||
}
|
||||
|
||||
static const SingleTouchData&
|
||||
GetTouchByID(const SingleTouchData& aCurrentTouch, MultiTouchInput& aOtherTouch)
|
||||
{
|
||||
int32_t id = aCurrentTouch.mIdentifier;
|
||||
for (size_t i = 0; i < aOtherTouch.mTouches.Length(); i++) {
|
||||
SingleTouchData& touch = aOtherTouch.mTouches[i];
|
||||
if (touch.mIdentifier == id) {
|
||||
return touch;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
static void
|
||||
ResampleTouch(MultiTouchInput& aOutTouch, MultiTouchInput& aCurrent,
|
||||
MultiTouchInput& aOther, int64_t aFrameDiff,
|
||||
int64_t aTouchDiff, bool aInterpolate)
|
||||
{
|
||||
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& other = GetTouchByID(current, aOther);
|
||||
|
||||
const ScreenIntPoint& currentTouchPoint = current.mScreenPoint;
|
||||
const ScreenIntPoint& otherTouchPoint = other.mScreenPoint;
|
||||
|
||||
ScreenIntPoint newSamplePoint;
|
||||
newSamplePoint.x = Interpolate(currentTouchPoint.x, otherTouchPoint.x, aFrameDiff, aTouchDiff);
|
||||
newSamplePoint.y = Interpolate(currentTouchPoint.y, otherTouchPoint.y, aFrameDiff, aTouchDiff);
|
||||
|
||||
aOutTouch.mTouches[i].mScreenPoint = newSamplePoint;
|
||||
|
||||
#ifdef LOG_RESAMPLE_DATA
|
||||
const char* type = "extrapolate";
|
||||
if (aInterpolate) {
|
||||
type = "interpolate";
|
||||
}
|
||||
|
||||
float alpha = (double) aFrameDiff / (double) aTouchDiff;
|
||||
LOG("%s current (%d, %d), other (%d, %d) to (%d, %d) alpha %f, touch diff %llu, frame diff %lld\n",
|
||||
type,
|
||||
currentTouchPoint.x, currentTouchPoint.y,
|
||||
otherTouchPoint.x, otherTouchPoint.y,
|
||||
newSamplePoint.x, newSamplePoint.y,
|
||||
alpha, aTouchDiff, aFrameDiff);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Interpolates with the touch event prior to SampleTime
|
||||
// and with the future touch event past sample time
|
||||
int32_t
|
||||
GeckoTouchDispatcher::InterpolateTouch(MultiTouchInput& aOutTouch, uint64_t aSampleTime)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(mTouchMoveEvents.size() >= 2);
|
||||
mTouchQueueLock.AssertCurrentThreadOwns();
|
||||
|
||||
// currentTouch < SampleTime < futureTouch
|
||||
MultiTouchInput futureTouch = mTouchMoveEvents.back();
|
||||
mTouchMoveEvents.pop_back();
|
||||
MultiTouchInput currentTouch = mTouchMoveEvents.back();
|
||||
|
||||
mTouchMoveEvents.clear();
|
||||
mTouchMoveEvents.push_back(futureTouch);
|
||||
|
||||
uint64_t currentTouchTime = mLastTouchTime - mTouchTimeDiff;
|
||||
int64_t frameDiff = aSampleTime - currentTouchTime;
|
||||
ResampleTouch(aOutTouch, currentTouch, futureTouch, frameDiff, mTouchTimeDiff, true);
|
||||
|
||||
return nanosecToMillisec(frameDiff);
|
||||
}
|
||||
|
||||
// Extrapolates from the previous two touch events before sample time
|
||||
// and extrapolates them to sample time.
|
||||
int32_t
|
||||
GeckoTouchDispatcher::ExtrapolateTouch(MultiTouchInput& aOutTouch, uint64_t aSampleTime)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(mTouchMoveEvents.size() >= 2);
|
||||
mTouchQueueLock.AssertCurrentThreadOwns();
|
||||
|
||||
// prevTouch < currentTouch < SampleTime
|
||||
MultiTouchInput currentTouch = mTouchMoveEvents.back();
|
||||
mTouchMoveEvents.pop_back();
|
||||
MultiTouchInput prevTouch = mTouchMoveEvents.back();
|
||||
mTouchMoveEvents.clear();
|
||||
mTouchMoveEvents.push_back(currentTouch);
|
||||
|
||||
uint64_t currentTouchTime = mLastTouchTime;
|
||||
int64_t maxResampleTime = std::min(mTouchTimeDiff / 2, (int64_t) mMaxPredict);
|
||||
uint64_t maxTimestamp = currentTouchTime + maxResampleTime;
|
||||
|
||||
if (aSampleTime > maxTimestamp) {
|
||||
aSampleTime = maxTimestamp;
|
||||
#ifdef LOG_RESAMPLE_DATA
|
||||
LOG("Overshot extrapolation time, adjusting sample time\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
// This has to be signed int since it is negative
|
||||
int64_t frameDiff = currentTouchTime - aSampleTime;
|
||||
ResampleTouch(aOutTouch, currentTouch, prevTouch, frameDiff, mTouchTimeDiff, false);
|
||||
return -nanosecToMillisec(frameDiff);
|
||||
}
|
||||
|
||||
void
|
||||
GeckoTouchDispatcher::ResampleTouchMoves(MultiTouchInput& aOutTouch, uint64_t aVsyncTime)
|
||||
{
|
||||
uint64_t sampleTime = aVsyncTime - mVsyncAdjust;
|
||||
int32_t touchTimeAdjust = 0;
|
||||
|
||||
if (mLastTouchTime > sampleTime) {
|
||||
touchTimeAdjust = InterpolateTouch(aOutTouch, sampleTime);
|
||||
} else {
|
||||
touchTimeAdjust = ExtrapolateTouch(aOutTouch, sampleTime);
|
||||
}
|
||||
|
||||
aOutTouch.mTimeStamp += TimeDuration::FromMilliseconds(touchTimeAdjust);
|
||||
aOutTouch.mTime += touchTimeAdjust;
|
||||
}
|
||||
|
||||
// Some touch events get sent as mouse events. If APZ doesn't capture the event
|
||||
// and if a touch only has 1 touch input, we can send a mouse event.
|
||||
void
|
||||
GeckoTouchDispatcher::DispatchMouseEvent(MultiTouchInput& aMultiTouch,
|
||||
bool aForwardToChildren)
|
||||
{
|
||||
WidgetMouseEvent mouseEvent = ToWidgetMouseEvent(aMultiTouch, nullptr);
|
||||
if (mouseEvent.message == NS_EVENT_NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
mouseEvent.mFlags.mNoCrossProcessBoundaryForwarding = !aForwardToChildren;
|
||||
nsWindow::DispatchInputEvent(mouseEvent);
|
||||
}
|
||||
|
||||
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 (!mTouchDownCount) {
|
||||
mTouchEventsFiltered = IsExpired(aMultiTouch);
|
||||
}
|
||||
|
||||
switch (aMultiTouch.mType) {
|
||||
case MultiTouchInput::MULTITOUCH_START:
|
||||
mTouchDownCount++;
|
||||
break;
|
||||
case MultiTouchInput::MULTITOUCH_MOVE:
|
||||
break;
|
||||
case MultiTouchInput::MULTITOUCH_END:
|
||||
case MultiTouchInput::MULTITOUCH_CANCEL:
|
||||
mTouchDownCount--;
|
||||
if (mTouchDownCount == 0) {
|
||||
MutexAutoLock lock(mTouchQueueLock);
|
||||
mTouchMoveEvents.clear();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (mTouchEventsFiltered) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool captured = false;
|
||||
WidgetTouchEvent event = aMultiTouch.ToWidgetTouchEvent(nullptr);
|
||||
nsEventStatus status = nsWindow::DispatchInputEvent(event, &captured);
|
||||
|
||||
if (mEnabledUniformityInfo) {
|
||||
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;
|
||||
}
|
||||
|
||||
const SingleTouchData& firstTouch = aMultiTouch.mTouches[0];
|
||||
const ScreenIntPoint& touchPoint = firstTouch.mScreenPoint;
|
||||
|
||||
LOG("UniformityInfo %s %llu %d %d", touchAction, systemTime(SYSTEM_TIME_MONOTONIC),
|
||||
touchPoint.x, touchPoint.y);
|
||||
}
|
||||
|
||||
if (!captured && (aMultiTouch.mTouches.Length() == 1)) {
|
||||
bool forwardToChildren = status != nsEventStatus_eConsumeNoDefault;
|
||||
DispatchMouseEvent(aMultiTouch, forwardToChildren);
|
||||
}
|
||||
}
|
||||
|
||||
WidgetMouseEvent
|
||||
GeckoTouchDispatcher::ToWidgetMouseEvent(const MultiTouchInput& aMultiTouch,
|
||||
nsIWidget* aWidget) const
|
||||
{
|
||||
NS_ABORT_IF_FALSE(NS_IsMainThread(),
|
||||
"Can only convert To WidgetMouseEvent on main thread");
|
||||
|
||||
uint32_t mouseEventType = NS_EVENT_NULL;
|
||||
switch (aMultiTouch.mType) {
|
||||
case MultiTouchInput::MULTITOUCH_START:
|
||||
mouseEventType = NS_MOUSE_BUTTON_DOWN;
|
||||
break;
|
||||
case MultiTouchInput::MULTITOUCH_MOVE:
|
||||
mouseEventType = NS_MOUSE_MOVE;
|
||||
break;
|
||||
case MultiTouchInput::MULTITOUCH_CANCEL:
|
||||
case MultiTouchInput::MULTITOUCH_END:
|
||||
mouseEventType = NS_MOUSE_BUTTON_UP;
|
||||
break;
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("Did not assign a type to WidgetMouseEvent");
|
||||
break;
|
||||
}
|
||||
|
||||
WidgetMouseEvent event(true, mouseEventType, aWidget,
|
||||
WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);
|
||||
|
||||
const SingleTouchData& firstTouch = aMultiTouch.mTouches[0];
|
||||
event.refPoint.x = firstTouch.mScreenPoint.x;
|
||||
event.refPoint.y = firstTouch.mScreenPoint.y;
|
||||
|
||||
event.time = aMultiTouch.mTime;
|
||||
event.button = WidgetMouseEvent::eLeftButton;
|
||||
event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
|
||||
event.modifiers = aMultiTouch.modifiers;
|
||||
|
||||
if (mouseEventType != NS_MOUSE_MOVE) {
|
||||
event.clickCount = 1;
|
||||
}
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,87 @@
|
|||
/* -*- 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.
|
||||
*/
|
||||
|
||||
#ifndef GECKO_TOUCH_INPUT_DISPATCHER_h
|
||||
#define GECKO_TOUCH_INPUT_DISPATCHER_h
|
||||
|
||||
#include "InputData.h"
|
||||
#include "Units.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include <vector>
|
||||
|
||||
class nsIWidget;
|
||||
|
||||
namespace mozilla {
|
||||
class WidgetMouseEvent;
|
||||
|
||||
// Used to resample touch events whenever a vsync event occurs. It batches
|
||||
// touch moves and on every vsync, resamples the touch position to create smooth
|
||||
// scrolls. We use the Android touch resample algorithm. It uses a combination of
|
||||
// extrapolation and interpolation. The algorithm takes the vsync time and
|
||||
// subtracts mVsyncAdjust time in ms and creates a sample time. All touch events are
|
||||
// relative to this sample time. If the last touch event occurs AFTER this
|
||||
// sample time, interpolate the last two touch events. If the last touch event occurs BEFORE
|
||||
// this sample time, we extrapolate the last two touch events to the sample
|
||||
// time. The magic numbers defined as constants are taken from android
|
||||
// InputTransport.cpp.
|
||||
class GeckoTouchDispatcher
|
||||
{
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GeckoTouchDispatcher)
|
||||
|
||||
public:
|
||||
GeckoTouchDispatcher();
|
||||
void NotifyTouch(MultiTouchInput& aData, uint64_t aEventTime);
|
||||
void DispatchTouchEvent(MultiTouchInput& aMultiTouch);
|
||||
void DispatchTouchMoveEvents(uint64_t aVsyncTime);
|
||||
static bool NotifyVsync(uint64_t aVsyncTimestamp);
|
||||
|
||||
private:
|
||||
int32_t InterpolateTouch(MultiTouchInput& aOutTouch, uint64_t aSampleTime);
|
||||
int32_t ExtrapolateTouch(MultiTouchInput& aOutTouch, uint64_t aSampleTime);
|
||||
void ResampleTouchMoves(MultiTouchInput& aOutTouch, uint64_t vsyncTime);
|
||||
void SendTouchEvent(MultiTouchInput& aData);
|
||||
void DispatchMouseEvent(MultiTouchInput& aMultiTouch,
|
||||
bool aForwardToChildren);
|
||||
WidgetMouseEvent ToWidgetMouseEvent(const MultiTouchInput& aData, nsIWidget* aWidget) const;
|
||||
|
||||
// mTouchQueueLock are used to protect the vector below
|
||||
// as it is accessed on the vsync thread and main thread
|
||||
Mutex mTouchQueueLock;
|
||||
std::vector<MultiTouchInput> mTouchMoveEvents;
|
||||
|
||||
bool mResamplingEnabled;
|
||||
bool mTouchEventsFiltered;
|
||||
bool mEnabledUniformityInfo;
|
||||
int mTouchDownCount;
|
||||
|
||||
// All times below are in nanoseconds
|
||||
int32_t mVsyncAdjust; // Time from vsync we create sample times from
|
||||
int32_t mMaxPredict; // How far into the future we're allowed to extrapolate
|
||||
|
||||
// Amount of time between vsync and the last event that is required before we
|
||||
// resample
|
||||
int32_t mMinResampleTime;
|
||||
|
||||
// The time difference between the last two touch move events
|
||||
int64_t mTouchTimeDiff;
|
||||
|
||||
// The system time at which the last touch event occured
|
||||
uint64_t mLastTouchTime;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
#endif // GECKO_TOUCH_INPUT_DISPATCHER_h
|
|
@ -28,6 +28,7 @@
|
|||
#include "mozilla/StaticPtr.h"
|
||||
#include "cutils/properties.h"
|
||||
#include "gfx2DGlue.h"
|
||||
#include "GeckoTouchDispatcher.h"
|
||||
|
||||
#if ANDROID_VERSION >= 17
|
||||
#include "libdisplay/FramebufferSurface.h"
|
||||
|
@ -149,6 +150,10 @@ HwcComposer2D::Init(hwc_display_t dpy, hwc_surface_t sur, gl::GLContext* aGLCont
|
|||
mColorFill = false;
|
||||
mRBSwapSupport = false;
|
||||
}
|
||||
|
||||
if (RegisterHwcEventCallback()) {
|
||||
EnableVsync(true);
|
||||
}
|
||||
#else
|
||||
char propValue[PROPERTY_VALUE_MAX];
|
||||
property_get("ro.display.colorfill", propValue, "0");
|
||||
|
@ -223,7 +228,7 @@ HwcComposer2D::RunVsyncEventControl(bool aEnable)
|
|||
void
|
||||
HwcComposer2D::Vsync(int aDisplay, int64_t aTimestamp)
|
||||
{
|
||||
// TODO: Handle Vsync event here
|
||||
GeckoTouchDispatcher::NotifyVsync(aTimestamp);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
@ -44,6 +44,7 @@ SOURCES += ['libui/' + src for src in [
|
|||
]]
|
||||
|
||||
SOURCES += [
|
||||
'GeckoTouchDispatcher.cpp',
|
||||
'GfxInfo.cpp',
|
||||
'GonkMemoryPressureMonitoring.cpp',
|
||||
'GonkPermission.cpp',
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
|
||||
// Defines kKeyMapping and GetKeyNameIndex()
|
||||
#include "GonkKeyMapping.h"
|
||||
#include "GeckoTouchDispatcher.h"
|
||||
|
||||
#define LOG(args...) \
|
||||
__android_log_print(ANDROID_LOG_INFO, "Gonk" , ## args)
|
||||
|
@ -157,14 +158,12 @@ struct UserInputData {
|
|||
::Touch touches[MAX_POINTERS];
|
||||
} motion;
|
||||
};
|
||||
|
||||
Modifiers DOMModifiers() const;
|
||||
};
|
||||
|
||||
Modifiers
|
||||
UserInputData::DOMModifiers() const
|
||||
static mozilla::Modifiers
|
||||
getDOMModifiers(int32_t metaState)
|
||||
{
|
||||
Modifiers result = 0;
|
||||
mozilla::Modifiers result = 0;
|
||||
if (metaState & (AMETA_ALT_ON | AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) {
|
||||
result |= MODIFIER_ALT;
|
||||
}
|
||||
|
@ -195,106 +194,6 @@ UserInputData::DOMModifiers() const
|
|||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
sendMouseEvent(uint32_t msg, UserInputData& data, bool forwardToChildren)
|
||||
{
|
||||
WidgetMouseEvent event(true, msg, nullptr,
|
||||
WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);
|
||||
|
||||
event.refPoint.x = data.motion.touches[0].coords.getX();
|
||||
event.refPoint.y = data.motion.touches[0].coords.getY();
|
||||
event.time = data.timeMs;
|
||||
event.button = WidgetMouseEvent::eLeftButton;
|
||||
event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
|
||||
if (msg != NS_MOUSE_MOVE)
|
||||
event.clickCount = 1;
|
||||
event.modifiers = data.DOMModifiers();
|
||||
|
||||
event.mFlags.mNoCrossProcessBoundaryForwarding = !forwardToChildren;
|
||||
|
||||
nsWindow::DispatchInputEvent(event);
|
||||
}
|
||||
|
||||
static void
|
||||
addDOMTouch(UserInputData& data, WidgetTouchEvent& event, int i)
|
||||
{
|
||||
const ::Touch& touch = data.motion.touches[i];
|
||||
event.touches.AppendElement(
|
||||
new dom::Touch(touch.id,
|
||||
nsIntPoint(floor(touch.coords.getX() + 0.5), floor(touch.coords.getY() + 0.5)),
|
||||
nsIntPoint(touch.coords.getAxisValue(AMOTION_EVENT_AXIS_SIZE),
|
||||
touch.coords.getAxisValue(AMOTION_EVENT_AXIS_SIZE)),
|
||||
0,
|
||||
touch.coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE))
|
||||
);
|
||||
}
|
||||
|
||||
static void
|
||||
printUniformityInfo(UserInputData& aData)
|
||||
{
|
||||
char* touchAction;
|
||||
const ::Touch& touch = aData.motion.touches[0];
|
||||
int32_t action = aData.action & AMOTION_EVENT_ACTION_MASK;
|
||||
switch (action) {
|
||||
case AMOTION_EVENT_ACTION_DOWN:
|
||||
touchAction = "Touch_Event_Down";
|
||||
break;
|
||||
case AMOTION_EVENT_ACTION_MOVE:
|
||||
touchAction = "Touch_Event_Move";
|
||||
break;
|
||||
case AMOTION_EVENT_ACTION_UP:
|
||||
touchAction = "Touch_Event_Up";
|
||||
break;
|
||||
default :
|
||||
return;
|
||||
}
|
||||
LOG("UniformityInfo %s %llu %f %f", touchAction, systemTime(SYSTEM_TIME_MONOTONIC),
|
||||
touch.coords.getX(), touch.coords.getY() );
|
||||
}
|
||||
|
||||
static nsEventStatus
|
||||
sendTouchEvent(UserInputData& data, bool* captured)
|
||||
{
|
||||
uint32_t msg;
|
||||
int32_t action = data.action & AMOTION_EVENT_ACTION_MASK;
|
||||
switch (action) {
|
||||
case AMOTION_EVENT_ACTION_DOWN:
|
||||
case AMOTION_EVENT_ACTION_POINTER_DOWN:
|
||||
msg = NS_TOUCH_START;
|
||||
break;
|
||||
case AMOTION_EVENT_ACTION_MOVE:
|
||||
msg = NS_TOUCH_MOVE;
|
||||
break;
|
||||
case AMOTION_EVENT_ACTION_UP:
|
||||
case AMOTION_EVENT_ACTION_POINTER_UP:
|
||||
msg = NS_TOUCH_END;
|
||||
break;
|
||||
case AMOTION_EVENT_ACTION_OUTSIDE:
|
||||
case AMOTION_EVENT_ACTION_CANCEL:
|
||||
msg = NS_TOUCH_CANCEL;
|
||||
break;
|
||||
default:
|
||||
return nsEventStatus_eIgnore;
|
||||
}
|
||||
|
||||
WidgetTouchEvent event(true, msg, nullptr);
|
||||
|
||||
event.time = data.timeMs;
|
||||
event.modifiers = data.DOMModifiers();
|
||||
|
||||
int32_t i;
|
||||
if (msg == NS_TOUCH_END) {
|
||||
i = data.action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK;
|
||||
i >>= AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
|
||||
addDOMTouch(data, event, i);
|
||||
} else {
|
||||
for (i = 0; i < data.motion.touchCount; ++i)
|
||||
addDOMTouch(data, event, i);
|
||||
}
|
||||
|
||||
return nsWindow::DispatchInputEvent(event, captured);
|
||||
}
|
||||
|
||||
class MOZ_STACK_CLASS KeyEventDispatcher
|
||||
{
|
||||
public:
|
||||
|
@ -405,7 +304,7 @@ KeyEventDispatcher::DispatchKeyEventInternal(uint32_t aEventMessage)
|
|||
event.mKeyValue = mDOMPrintableKeyValue;
|
||||
}
|
||||
event.mCodeNameIndex = mDOMCodeNameIndex;
|
||||
event.modifiers = mData.DOMModifiers();
|
||||
event.modifiers = getDOMModifiers(mData.metaState);
|
||||
event.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_MOBILE;
|
||||
event.time = mData.timeMs;
|
||||
return nsWindow::DispatchInputEvent(event);
|
||||
|
@ -594,12 +493,10 @@ public:
|
|||
GeckoInputDispatcher(sp<EventHub> &aEventHub)
|
||||
: mQueueLock("GeckoInputDispatcher::mQueueMutex")
|
||||
, mEventHub(aEventHub)
|
||||
, mTouchDownCount(0)
|
||||
, mKeyDownCount(0)
|
||||
, mTouchEventsFiltered(false)
|
||||
, mKeyEventsFiltered(false)
|
||||
{
|
||||
mEnabledUniformityInfo = Preferences::GetBool("layers.uniformity-info", false);
|
||||
mTouchDispatcher = new GeckoTouchDispatcher();
|
||||
}
|
||||
|
||||
virtual void dump(String8& dump);
|
||||
|
@ -644,13 +541,10 @@ private:
|
|||
mozilla::Mutex mQueueLock;
|
||||
std::queue<UserInputData> mEventQueue;
|
||||
sp<EventHub> mEventHub;
|
||||
nsRefPtr<GeckoTouchDispatcher> mTouchDispatcher;
|
||||
|
||||
int mTouchDownCount;
|
||||
int mKeyDownCount;
|
||||
bool mTouchEventsFiltered;
|
||||
bool mKeyEventsFiltered;
|
||||
BitSet32 mTouchDown;
|
||||
bool mEnabledUniformityInfo;
|
||||
};
|
||||
|
||||
// GeckoInputReaderPolicy
|
||||
|
@ -722,80 +616,7 @@ GeckoInputDispatcher::dispatchOnce()
|
|||
|
||||
switch (data.type) {
|
||||
case UserInputData::MOTION_DATA: {
|
||||
if (!mTouchDownCount) {
|
||||
// No pending events, the filter state can be updated.
|
||||
mTouchEventsFiltered = isExpired(data);
|
||||
}
|
||||
|
||||
int32_t action = data.action & AMOTION_EVENT_ACTION_MASK;
|
||||
int32_t index = data.action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK;
|
||||
index >>= AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
|
||||
int32_t id = data.motion.touches[index].id;
|
||||
switch (action) {
|
||||
case AMOTION_EVENT_ACTION_DOWN:
|
||||
case AMOTION_EVENT_ACTION_POINTER_DOWN:
|
||||
if (!mTouchDown.hasBit(id)) {
|
||||
mTouchDown.markBit(id);
|
||||
mTouchDownCount++;
|
||||
}
|
||||
break;
|
||||
case AMOTION_EVENT_ACTION_MOVE:
|
||||
case AMOTION_EVENT_ACTION_HOVER_MOVE:
|
||||
// No need to update the count on move.
|
||||
break;
|
||||
case AMOTION_EVENT_ACTION_UP:
|
||||
case AMOTION_EVENT_ACTION_POINTER_UP:
|
||||
case AMOTION_EVENT_ACTION_OUTSIDE:
|
||||
case AMOTION_EVENT_ACTION_CANCEL:
|
||||
if (mTouchDown.hasBit(id)) {
|
||||
mTouchDown.clearBit(id);
|
||||
mTouchDownCount--;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (mTouchEventsFiltered) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsEventStatus status = nsEventStatus_eIgnore;
|
||||
if (action != AMOTION_EVENT_ACTION_HOVER_MOVE) {
|
||||
bool captured;
|
||||
status = sendTouchEvent(data, &captured);
|
||||
if (mEnabledUniformityInfo) {
|
||||
printUniformityInfo(data);
|
||||
}
|
||||
if (captured) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t msg;
|
||||
switch (action) {
|
||||
case AMOTION_EVENT_ACTION_DOWN:
|
||||
msg = NS_MOUSE_BUTTON_DOWN;
|
||||
break;
|
||||
case AMOTION_EVENT_ACTION_POINTER_DOWN:
|
||||
case AMOTION_EVENT_ACTION_POINTER_UP:
|
||||
case AMOTION_EVENT_ACTION_MOVE:
|
||||
case AMOTION_EVENT_ACTION_HOVER_MOVE:
|
||||
msg = NS_MOUSE_MOVE;
|
||||
break;
|
||||
case AMOTION_EVENT_ACTION_OUTSIDE:
|
||||
case AMOTION_EVENT_ACTION_CANCEL:
|
||||
case AMOTION_EVENT_ACTION_UP:
|
||||
msg = NS_MOUSE_BUTTON_UP;
|
||||
break;
|
||||
default:
|
||||
msg = NS_EVENT_NULL;
|
||||
break;
|
||||
}
|
||||
if (msg != NS_EVENT_NULL) {
|
||||
sendMouseEvent(msg, data,
|
||||
status != nsEventStatus_eConsumeNoDefault);
|
||||
}
|
||||
MOZ_ASSERT_UNREACHABLE("Should not dispatch touch events here anymore");
|
||||
break;
|
||||
}
|
||||
case UserInputData::KEY_DATA: {
|
||||
|
@ -841,41 +662,72 @@ GeckoInputDispatcher::notifyKey(const NotifyKeyArgs* args)
|
|||
gAppShell->NotifyNativeEvent();
|
||||
}
|
||||
|
||||
static void
|
||||
addMultiTouch(MultiTouchInput& aMultiTouch,
|
||||
const NotifyMotionArgs* args, int aIndex)
|
||||
{
|
||||
int32_t id = args->pointerProperties[aIndex].id;
|
||||
PointerCoords coords = args->pointerCoords[aIndex];
|
||||
float force = coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
|
||||
ScreenIntPoint point(floor(coords.getX() + 0.5),
|
||||
floor(coords.getY() + 0.5));
|
||||
|
||||
SingleTouchData touchData(id, point, ScreenSize(0, 0),
|
||||
0, force);
|
||||
|
||||
aMultiTouch.mTouches.AppendElement(touchData);
|
||||
}
|
||||
|
||||
void
|
||||
GeckoInputDispatcher::notifyMotion(const NotifyMotionArgs* args)
|
||||
{
|
||||
UserInputData data;
|
||||
data.timeMs = nanosecsToMillisecs(args->eventTime);
|
||||
data.type = UserInputData::MOTION_DATA;
|
||||
data.action = args->action;
|
||||
data.flags = args->flags;
|
||||
data.metaState = args->metaState;
|
||||
data.deviceId = args->deviceId;
|
||||
MOZ_ASSERT(args->pointerCount <= MAX_POINTERS);
|
||||
data.motion.touchCount = args->pointerCount;
|
||||
for (uint32_t i = 0; i < args->pointerCount; ++i) {
|
||||
::Touch& touch = data.motion.touches[i];
|
||||
touch.id = args->pointerProperties[i].id;
|
||||
memcpy(&touch.coords, &args->pointerCoords[i], sizeof(*args->pointerCoords));
|
||||
uint32_t time = nanosecsToMillisecs(args->eventTime);
|
||||
int32_t action = args->action & AMOTION_EVENT_ACTION_MASK;
|
||||
int touchCount = args->pointerCount;
|
||||
MOZ_ASSERT(touchCount <= MAX_POINTERS);
|
||||
TimeStamp timestamp = TimeStamp::Now();
|
||||
Modifiers modifiers = getDOMModifiers(args->metaState);
|
||||
|
||||
MultiTouchInput::MultiTouchType touchType = MultiTouchInput::MULTITOUCH_CANCEL;
|
||||
switch (action) {
|
||||
case AMOTION_EVENT_ACTION_DOWN:
|
||||
case AMOTION_EVENT_ACTION_POINTER_DOWN:
|
||||
touchType = MultiTouchInput::MULTITOUCH_START;
|
||||
break;
|
||||
case AMOTION_EVENT_ACTION_MOVE:
|
||||
touchType = MultiTouchInput::MULTITOUCH_MOVE;
|
||||
break;
|
||||
case AMOTION_EVENT_ACTION_UP:
|
||||
case AMOTION_EVENT_ACTION_POINTER_UP:
|
||||
touchType = MultiTouchInput::MULTITOUCH_END;
|
||||
break;
|
||||
case AMOTION_EVENT_ACTION_OUTSIDE:
|
||||
case AMOTION_EVENT_ACTION_CANCEL:
|
||||
touchType = MultiTouchInput::MULTITOUCH_CANCEL;
|
||||
break;
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("Could not assign a touch type");
|
||||
break;
|
||||
}
|
||||
{
|
||||
MutexAutoLock lock(mQueueLock);
|
||||
if (!mEventQueue.empty() &&
|
||||
mEventQueue.back().type == UserInputData::MOTION_DATA &&
|
||||
((mEventQueue.back().action & AMOTION_EVENT_ACTION_MASK) ==
|
||||
AMOTION_EVENT_ACTION_MOVE ||
|
||||
(mEventQueue.back().action & AMOTION_EVENT_ACTION_MASK) ==
|
||||
AMOTION_EVENT_ACTION_HOVER_MOVE))
|
||||
mEventQueue.back() = data;
|
||||
else
|
||||
mEventQueue.push(data);
|
||||
|
||||
MultiTouchInput touchData(touchType, time, timestamp, modifiers);
|
||||
|
||||
// For touch ends, we have to filter out which finger is actually
|
||||
// the touch end since the touch array has all fingers, not just the touch
|
||||
// that we want to end
|
||||
if (touchType == MultiTouchInput::MULTITOUCH_END) {
|
||||
int touchIndex = args->action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK;
|
||||
touchIndex >>= AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
|
||||
addMultiTouch(touchData, args, touchIndex);
|
||||
} else {
|
||||
for (int32_t i = 0; i < touchCount; ++i) {
|
||||
addMultiTouch(touchData, args, i);
|
||||
}
|
||||
}
|
||||
gAppShell->NotifyNativeEvent();
|
||||
|
||||
mTouchDispatcher->NotifyTouch(touchData, args->eventTime);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void GeckoInputDispatcher::notifySwitch(const NotifySwitchArgs* args)
|
||||
{
|
||||
if (!sDevInputAudioJack)
|
||||
|
|
Загрузка…
Ссылка в новой задаче