blob: 2b3d2c39c6661d3d719a1c4becd48f8b4fc226a3 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/events/gesture_detection/motion_event_buffer.h"
#include "base/trace_event/trace_event.h"
#include "ui/events/gesture_detection/motion_event.h"
#include "ui/events/gesture_detection/motion_event_generic.h"
namespace ui {
namespace {
// Latency added during resampling. A few milliseconds doesn't hurt much but
// reduces the impact of mispredicted touch positions.
const int kResampleLatencyMs = 5;
// Minimum time difference between consecutive samples before attempting to
// resample.
const int kResampleMinDeltaMs = 2;
// Maximum time to predict forward from the last known state, to avoid
// predicting too far into the future. This time is further bounded by 50% of
// the last time delta.
const int kResampleMaxPredictionMs = 8;
typedef ScopedVector<MotionEvent> MotionEventVector;
float Lerp(float a, float b, float alpha) {
return a + alpha * (b - a);
}
bool CanAddSample(const MotionEvent& event0, const MotionEvent& event1) {
DCHECK_EQ(event0.GetAction(), MotionEvent::ACTION_MOVE);
if (event1.GetAction() != MotionEvent::ACTION_MOVE)
return false;
const size_t pointer_count = event0.GetPointerCount();
if (pointer_count != event1.GetPointerCount())
return false;
for (size_t event0_i = 0; event0_i < pointer_count; ++event0_i) {
const int id = event0.GetPointerId(event0_i);
const int event1_i = event1.FindPointerIndexOfId(id);
if (event1_i == -1)
return false;
if (event0.GetToolType(event0_i) != event1.GetToolType(event1_i))
return false;
}
return true;
}
bool ShouldResampleTool(MotionEvent::ToolType tool) {
return tool == MotionEvent::TOOL_TYPE_UNKNOWN ||
tool == MotionEvent::TOOL_TYPE_FINGER;
}
size_t CountSamplesNoLaterThan(const MotionEventVector& batch,
base::TimeTicks time) {
size_t count = 0;
while (count < batch.size() && batch[count]->GetEventTime() <= time)
++count;
return count;
}
MotionEventVector ConsumeSamplesNoLaterThan(MotionEventVector* batch,
base::TimeTicks time) {
DCHECK(batch);
size_t count = CountSamplesNoLaterThan(*batch, time);
DCHECK_GE(batch->size(), count);
if (count == 0)
return MotionEventVector();
if (count == batch->size())
return batch->Pass();
// TODO(jdduke): Use a ScopedDeque to work around this mess.
MotionEventVector unconsumed_batch;
unconsumed_batch.insert(
unconsumed_batch.begin(), batch->begin() + count, batch->end());
batch->weak_erase(batch->begin() + count, batch->end());
unconsumed_batch.swap(*batch);
DCHECK_GE(unconsumed_batch.size(), 1U);
return unconsumed_batch.Pass();
}
PointerProperties PointerFromMotionEvent(const MotionEvent& event,
size_t pointer_index) {
PointerProperties result;
result.id = event.GetPointerId(pointer_index);
result.tool_type = event.GetToolType(pointer_index);
result.x = event.GetX(pointer_index);
result.y = event.GetY(pointer_index);
result.raw_x = event.GetRawX(pointer_index);
result.raw_y = event.GetRawY(pointer_index);
result.pressure = event.GetPressure(pointer_index);
result.touch_major = event.GetTouchMajor(pointer_index);
result.touch_minor = event.GetTouchMinor(pointer_index);
result.orientation = event.GetOrientation(pointer_index);
return result;
}
PointerProperties ResamplePointer(const MotionEvent& event0,
const MotionEvent& event1,
size_t event0_pointer_index,
size_t event1_pointer_index,
float alpha) {
DCHECK_EQ(event0.GetPointerId(event0_pointer_index),
event1.GetPointerId(event1_pointer_index));
// If the tool should not be resampled, use the latest event in the valid
// horizon (i.e., the event no later than the time interpolated by alpha).
if (!ShouldResampleTool(event0.GetToolType(event0_pointer_index))) {
if (alpha > 1)
return PointerFromMotionEvent(event1, event1_pointer_index);
else
return PointerFromMotionEvent(event0, event0_pointer_index);
}
PointerProperties p(PointerFromMotionEvent(event0, event0_pointer_index));
p.x = Lerp(p.x, event1.GetX(event1_pointer_index), alpha);
p.y = Lerp(p.y, event1.GetY(event1_pointer_index), alpha);
p.raw_x = Lerp(p.raw_x, event1.GetRawX(event1_pointer_index), alpha);
p.raw_y = Lerp(p.raw_y, event1.GetRawY(event1_pointer_index), alpha);
return p;
}
scoped_ptr<MotionEvent> ResampleMotionEvent(const MotionEvent& event0,
const MotionEvent& event1,
base::TimeTicks resample_time) {
DCHECK_EQ(MotionEvent::ACTION_MOVE, event0.GetAction());
DCHECK_EQ(event0.GetPointerCount(), event1.GetPointerCount());
const base::TimeTicks time0 = event0.GetEventTime();
const base::TimeTicks time1 = event1.GetEventTime();
DCHECK(time0 < time1);
DCHECK(time0 <= resample_time);
const float alpha = (resample_time - time0).InMillisecondsF() /
(time1 - time0).InMillisecondsF();
scoped_ptr<MotionEventGeneric> event;
const size_t pointer_count = event0.GetPointerCount();
DCHECK_EQ(pointer_count, event1.GetPointerCount());
for (size_t event0_i = 0; event0_i < pointer_count; ++event0_i) {
int event1_i = event1.FindPointerIndexOfId(event0.GetPointerId(event0_i));
DCHECK_NE(event1_i, -1);
PointerProperties pointer = ResamplePointer(
event0, event1, event0_i, static_cast<size_t>(event1_i), alpha);
if (event0_i == 0) {
event.reset(new MotionEventGeneric(
MotionEvent::ACTION_MOVE, resample_time, pointer));
} else {
event->PushPointer(pointer);
}
}
DCHECK(event);
event->set_id(event0.GetId());
event->set_action_index(event0.GetActionIndex());
event->set_button_state(event0.GetButtonState());
return event.Pass();
}
// MotionEvent implementation for storing multiple events, with the most
// recent event used as the base event, and prior events used as the history.
class CompoundMotionEvent : public ui::MotionEvent {
public:
explicit CompoundMotionEvent(MotionEventVector events)
: events_(events.Pass()) {
DCHECK_GE(events_.size(), 1U);
}
~CompoundMotionEvent() override {}
int GetId() const override { return latest().GetId(); }
Action GetAction() const override { return latest().GetAction(); }
int GetActionIndex() const override { return latest().GetActionIndex(); }
size_t GetPointerCount() const override { return latest().GetPointerCount(); }
int GetPointerId(size_t pointer_index) const override {
return latest().GetPointerId(pointer_index);
}
float GetX(size_t pointer_index) const override {
return latest().GetX(pointer_index);
}
float GetY(size_t pointer_index) const override {
return latest().GetY(pointer_index);
}
float GetRawX(size_t pointer_index) const override {
return latest().GetRawX(pointer_index);
}
float GetRawY(size_t pointer_index) const override {
return latest().GetRawY(pointer_index);
}
float GetTouchMajor(size_t pointer_index) const override {
return latest().GetTouchMajor(pointer_index);
}
float GetTouchMinor(size_t pointer_index) const override {
return latest().GetTouchMinor(pointer_index);
}
float GetOrientation(size_t pointer_index) const override {
return latest().GetOrientation(pointer_index);
}
float GetPressure(size_t pointer_index) const override {
return latest().GetPressure(pointer_index);
}
ToolType GetToolType(size_t pointer_index) const override {
return latest().GetToolType(pointer_index);
}
int GetButtonState() const override { return latest().GetButtonState(); }
int GetFlags() const override { return latest().GetFlags(); }
base::TimeTicks GetEventTime() const override {
return latest().GetEventTime();
}
size_t GetHistorySize() const override { return events_.size() - 1; }
base::TimeTicks GetHistoricalEventTime(
size_t historical_index) const override {
DCHECK_LT(historical_index, GetHistorySize());
return events_[historical_index]->GetEventTime();
}
float GetHistoricalTouchMajor(size_t pointer_index,
size_t historical_index) const override {
DCHECK_LT(historical_index, GetHistorySize());
return events_[historical_index]->GetTouchMajor();
}
float GetHistoricalX(size_t pointer_index,
size_t historical_index) const override {
DCHECK_LT(historical_index, GetHistorySize());
return events_[historical_index]->GetX(pointer_index);
}
float GetHistoricalY(size_t pointer_index,
size_t historical_index) const override {
DCHECK_LT(historical_index, GetHistorySize());
return events_[historical_index]->GetY(pointer_index);
}
scoped_ptr<MotionEvent> Clone() const override {
MotionEventVector cloned_events;
cloned_events.reserve(events_.size());
for (size_t i = 0; i < events_.size(); ++i)
cloned_events.push_back(events_[i]->Clone().release());
return scoped_ptr<MotionEvent>(
new CompoundMotionEvent(cloned_events.Pass()));
}
scoped_ptr<MotionEvent> Cancel() const override { return latest().Cancel(); }
// Returns the new, resampled event, or NULL if none was created.
// TODO(jdduke): Revisit resampling to handle cases where alternating frames
// are resampled or resampling is otherwise inconsistent, e.g., a 90hz input
// and 60hz frame signal could phase-align such that even frames yield an
// extrapolated event and odd frames are not resampled, crbug.com/399381.
const MotionEvent* TryResample(base::TimeTicks resample_time,
const ui::MotionEvent* next) {
DCHECK_EQ(GetAction(), ACTION_MOVE);
const ui::MotionEvent* event0 = NULL;
const ui::MotionEvent* event1 = NULL;
if (next) {
DCHECK(resample_time < next->GetEventTime());
// Interpolate between current sample and future sample.
event0 = events_.back();
event1 = next;
} else if (events_.size() >= 2) {
// Extrapolate future sample using current sample and past sample.
event0 = events_[events_.size() - 2];
event1 = events_[events_.size() - 1];
const base::TimeTicks time1 = event1->GetEventTime();
base::TimeTicks max_predict =
time1 +
std::min((event1->GetEventTime() - event0->GetEventTime()) / 2,
base::TimeDelta::FromMilliseconds(kResampleMaxPredictionMs));
if (resample_time > max_predict) {
TRACE_EVENT_INSTANT2("input",
"MotionEventBuffer::TryResample prediction adjust",
TRACE_EVENT_SCOPE_THREAD,
"original(ms)",
(resample_time - time1).InMilliseconds(),
"adjusted(ms)",
(max_predict - time1).InMilliseconds());
resample_time = max_predict;
}
} else {
TRACE_EVENT_INSTANT0("input",
"MotionEventBuffer::TryResample insufficient data",
TRACE_EVENT_SCOPE_THREAD);
return NULL;
}
DCHECK(event0);
DCHECK(event1);
const base::TimeTicks time0 = event0->GetEventTime();
const base::TimeTicks time1 = event1->GetEventTime();
base::TimeDelta delta = time1 - time0;
if (delta < base::TimeDelta::FromMilliseconds(kResampleMinDeltaMs)) {
TRACE_EVENT_INSTANT1("input",
"MotionEventBuffer::TryResample failure",
TRACE_EVENT_SCOPE_THREAD,
"event_delta_too_small(ms)",
delta.InMilliseconds());
return NULL;
}
events_.push_back(
ResampleMotionEvent(*event0, *event1, resample_time).release());
return events_.back();
}
size_t samples() const { return events_.size(); }
private:
const MotionEvent& latest() const { return *events_.back(); }
// Events are in order from oldest to newest.
MotionEventVector events_;
DISALLOW_COPY_AND_ASSIGN(CompoundMotionEvent);
};
} // namespace
MotionEventBuffer::MotionEventBuffer(MotionEventBufferClient* client,
bool enable_resampling)
: client_(client), resample_(enable_resampling) {
}
MotionEventBuffer::~MotionEventBuffer() {
}
void MotionEventBuffer::OnMotionEvent(const MotionEvent& event) {
if (event.GetAction() != MotionEvent::ACTION_MOVE) {
last_extrapolated_event_time_ = base::TimeTicks();
if (!buffered_events_.empty())
FlushWithoutResampling(buffered_events_.Pass());
client_->ForwardMotionEvent(event);
return;
}
// Guard against events that are *older* than the last one that may have been
// artificially synthesized.
if (!last_extrapolated_event_time_.is_null()) {
DCHECK(buffered_events_.empty());
if (event.GetEventTime() < last_extrapolated_event_time_)
return;
last_extrapolated_event_time_ = base::TimeTicks();
}
scoped_ptr<MotionEvent> clone = event.Clone();
if (buffered_events_.empty()) {
buffered_events_.push_back(clone.release());
client_->SetNeedsFlush();
return;
}
if (CanAddSample(*buffered_events_.front(), *clone)) {
DCHECK(buffered_events_.back()->GetEventTime() <= clone->GetEventTime());
} else {
FlushWithoutResampling(buffered_events_.Pass());
}
buffered_events_.push_back(clone.release());
// No need to request another flush as the first event will have requested it.
}
void MotionEventBuffer::Flush(base::TimeTicks frame_time) {
if (buffered_events_.empty())
return;
// Shifting the sample time back slightly minimizes the potential for
// misprediction when extrapolating events.
if (resample_)
frame_time -= base::TimeDelta::FromMilliseconds(kResampleLatencyMs);
// TODO(jdduke): Use a persistent MotionEventVector vector for temporary
// storage.
MotionEventVector events(
ConsumeSamplesNoLaterThan(&buffered_events_, frame_time));
if (events.empty()) {
DCHECK(!buffered_events_.empty());
client_->SetNeedsFlush();
return;
}
if (!resample_ || (events.size() == 1 && buffered_events_.empty())) {
FlushWithoutResampling(events.Pass());
if (!buffered_events_.empty())
client_->SetNeedsFlush();
return;
}
CompoundMotionEvent resampled_event(events.Pass());
base::TimeTicks original_event_time = resampled_event.GetEventTime();
const MotionEvent* next_event =
!buffered_events_.empty() ? buffered_events_.front() : NULL;
// Try to interpolate/extrapolate a new event at |frame_time|. Note that
// |new_event|, if non-NULL, is owned by |resampled_event_|.
const MotionEvent* new_event =
resampled_event.TryResample(frame_time, next_event);
// Log the extrapolated event time, guarding against subsequently queued
// events that might have an earlier timestamp.
if (!next_event && new_event &&
new_event->GetEventTime() > original_event_time) {
last_extrapolated_event_time_ = new_event->GetEventTime();
} else {
last_extrapolated_event_time_ = base::TimeTicks();
}
client_->ForwardMotionEvent(resampled_event);
if (!buffered_events_.empty())
client_->SetNeedsFlush();
}
void MotionEventBuffer::FlushWithoutResampling(MotionEventVector events) {
last_extrapolated_event_time_ = base::TimeTicks();
if (events.empty())
return;
if (events.size() == 1) {
// Avoid CompoundEvent creation to prevent unnecessary allocations.
scoped_ptr<MotionEvent> event(events.front());
events.weak_clear();
client_->ForwardMotionEvent(*event);
return;
}
CompoundMotionEvent compound_event(events.Pass());
client_->ForwardMotionEvent(compound_event);
}
} // namespace ui