summaryrefslogtreecommitdiffstats
path: root/widget/TouchResampler.h
blob: 69f544b55776e062688e53e3706d35fae7ea2ea4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
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