diff options
Diffstat (limited to '')
-rw-r--r-- | widget/TouchResampler.h | 191 |
1 files changed, 191 insertions, 0 deletions
diff --git a/widget/TouchResampler.h b/widget/TouchResampler.h new file mode 100644 index 0000000000..69f544b557 --- /dev/null +++ b/widget/TouchResampler.h @@ -0,0 +1,191 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_widget_TouchResampler_h +#define mozilla_widget_TouchResampler_h + +#include <queue> +#include <unordered_map> + +#include "mozilla/Maybe.h" +#include "mozilla/TimeStamp.h" +#include "InputData.h" + +namespace mozilla { +namespace widget { + +/** + * De-jitters touch motions by resampling (interpolating or extrapolating) touch + * positions for the vsync timestamp. + * + * Touch resampling improves the touch panning experience on devices where touch + * positions are sampled at a rate that's not an integer multiple of the display + * refresh rate, for example 100Hz touch sampling on a 60Hz display: Without + * resampling, we would alternate between taking one touch sample or two touch + * samples into account each frame, creating a jittery motion ("small step, big + * step, small step, big step"). + * Intended for use on Android, where both touch events and vsync notifications + * arrive on the same thread, the Java UI thread. + * This class is not thread safe. + * + * TouchResampler operates in the following way: + * + * Original events are fed into ProcessEvent(). + * Outgoing events (potentially resampled for resampling) are added to a queue + * and can be consumed by calling ConsumeOutgoingEvents(). Touch events which + * are not touch move events are forwarded instantly and not resampled. Only + * touch move events are resampled. Whenever a touch move event is received, it + * gets delayed until NotifyFrame() is called, at which point it is resampled + * into a resampled version for the given frame timestamp, and added to the + * outgoing queue. If no touch move event is received between two consecutive + * frames, this is treated as a stop in the touch motion. If the last outgoing + * event was an resampled touch move event, we return back to the non-resampled + * state by emitting a copy of the last original touch move event, which has + * unmodified position data. Touch events which are not touch move events also + * force a return to the non-resampled state before they are moved to the + * outgoing queue. + */ +class TouchResampler final { + public: + // Feed a touch event into the interpolater. Returns an ID that can be used to + // match outgoing events to this incoming event, to track data associated with + // this event. + uint64_t ProcessEvent(MultiTouchInput&& aInput); + + // Emit events, potentially resampled, for this timestamp. The events are put + // into the outgoing queue. May not emit any events if there's no update. + void NotifyFrame(const TimeStamp& aTimeStamp); + + // Returns true between the start and the end of a touch gesture. During this + // time, the caller should keep itself registered with the system frame + // callback mechanism, so that NotifyFrame() can be called on every frame. + // (Otherwise, if we only registered the callback after receiving a touch move + // event, the frame callback might be delayed by a full frame.) + bool InTouchingState() const { return mCurrentTouches.HasTouch(); } + + struct OutgoingEvent { + // The event, potentially modified from the original for resampling. + MultiTouchInput mEvent; + + // Some(eventId) if this event is a modified version of an original event, + // Nothing() if this is an extra event. + Maybe<uint64_t> mEventId; + }; + + // Returns the outgoing events that were produced since the last call. + // No event IDs will be skipped. Returns at least one outgoing event for each + // incoming event (possibly after a delay), and potential extra events with + // no originating event ID. + // Outgoing events should be consumed after every call to ProcessEvent() and + // after every call to NotifyFrame(). + std::queue<OutgoingEvent> ConsumeOutgoingEvents() { + return std::move(mOutgoingEvents); + } + + private: + // Add the event to the outgoing queue. + void EmitEvent(MultiTouchInput&& aInput, uint64_t aEventId) { + mLastEmittedEventTime = aInput.mTimeStamp; + mOutgoingEvents.push(OutgoingEvent{std::move(aInput), Some(aEventId)}); + } + + // Emit an event that does not correspond to an incoming event. + void EmitExtraEvent(MultiTouchInput&& aInput) { + mLastEmittedEventTime = aInput.mTimeStamp; + mOutgoingEvents.push(OutgoingEvent{std::move(aInput), Nothing()}); + } + + // Move any touch move events that we deferred for resampling to the outgoing + // queue unmodified, leaving mDeferredTouchMoveEvents empty. + void FlushDeferredTouchMoveEventsUnresampled(); + + // Must only be called if mInResampledState is true and + // mDeferredTouchMoveEvents is empty. Emits mOriginalOfResampledTouchMove, + // with a potentially adjusted timestamp for correct ordering. + void ReturnToNonResampledState(); + + // Takes historical touch data from mRemainingTouchData and prepends it to the + // data in aInput. + void PrependLeftoverHistoricalData(MultiTouchInput* aInput); + + struct DataPoint { + TimeStamp mTimeStamp; + ScreenIntPoint mPosition; + }; + + struct TouchInfo { + void Update(const SingleTouchData& aTouch, const TimeStamp& aEventTime); + ScreenIntPoint ResampleAtTime(const ScreenIntPoint& aLastObservedPosition, + const TimeStamp& aTimeStamp); + + int32_t mIdentifier = 0; + Maybe<DataPoint> mBaseDataPoint; + Maybe<DataPoint> mLatestDataPoint; + }; + + struct CurrentTouches { + void UpdateFromEvent(const MultiTouchInput& aInput); + bool HasTouch() const { return !mTouches.IsEmpty(); } + TimeStamp LatestDataPointTime() { return mLatestDataPointTime; } + + ScreenIntPoint ResampleTouchPositionAtTime( + int32_t aIdentifier, const ScreenIntPoint& aLastObservedPosition, + const TimeStamp& aTimeStamp); + + void ClearDataPoints() { + for (auto& touch : mTouches) { + touch.mBaseDataPoint = Nothing(); + touch.mLatestDataPoint = Nothing(); + } + } + + private: + nsTArray<TouchInfo>::iterator TouchByIdentifier(int32_t aIdentifier); + + nsTArray<TouchInfo> mTouches; + TimeStamp mLatestDataPointTime; + }; + + // The current touch positions with historical data points. This data only + // contains original non-resampled positions from the incoming touch events. + CurrentTouches mCurrentTouches; + + // Incoming touch move events are stored here until NotifyFrame is called. + std::queue<std::pair<MultiTouchInput, uint64_t>> mDeferredTouchMoveEvents; + + // Stores any touch samples that were not included in the last emitted touch + // move event because they were in the future compared to the emitted event's + // timestamp. These data points should be prepended to the historical data of + // the next emitted touch move evnt. + // Can only be non-empty if mInResampledState is true. + std::unordered_map<int32_t, nsTArray<SingleTouchData::HistoricalTouchData>> + mRemainingTouchData; + + // If we're in an resampled state, because the last outgoing event was a + // resampled touch move event, then this contains a copy of the unresampled, + // original touch move event. + // Some() iff mInResampledState is true. + Maybe<MultiTouchInput> mOriginalOfResampledTouchMove; + + // The stream of outgoing events that can be consumed by our caller. + std::queue<OutgoingEvent> mOutgoingEvents; + + // The timestamp of the event that was emitted most recently, or the null + // timestamp if no event has been emitted yet. + TimeStamp mLastEmittedEventTime; + + uint64_t mNextEventId = 0; + + // True if the last outgoing event was a touch move event with an resampled + // position. We only want to stay in this state as long as a continuous stream + // of touch move events is coming in. + bool mInResampledState = false; +}; + +} // namespace widget +} // namespace mozilla + +#endif // mozilla_widget_TouchResampler_h |