diff --git a/libs/global/CMakeLists.txt b/libs/global/CMakeLists.txt --- a/libs/global/CMakeLists.txt +++ b/libs/global/CMakeLists.txt @@ -19,6 +19,7 @@ kis_signal_compressor.cpp kis_signal_compressor_with_param.cpp kis_acyclic_signal_connector.cpp + kis_latency_tracker.cpp KisQPainterStateSaver.cpp KisLoggingManager.cpp ) diff --git a/libs/global/kis_latency_tracker.h b/libs/global/kis_latency_tracker.h new file mode 100644 --- /dev/null +++ b/libs/global/kis_latency_tracker.h @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2017 Bernhard Liebl + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KRITA_KIS_SCALAR_TRACKER_H +#define KRITA_KIS_SCALAR_TRACKER_H + +#include "kis_shared.h" +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +template +class KisRollingMax { +public: + KisRollingMax(int windowSize) : m_windowSize(windowSize) { + } + + void push(T value) { + while (m_samples.size() > m_windowSize) { + m_values.erase(m_samples.dequeue()); + } + + m_samples.enqueue(m_values.push(value)); + } + + T max() const { + if (m_values.empty()) { + throw std::runtime_error("no values to get max of"); + } else { + return m_values.top(); + } + } + +private: + const int m_windowSize; + + typedef boost::heap::fibonacci_heap heap_type; + + QQueue m_samples; + heap_type m_values; +}; + +template +class KisScalarTracker : public KisShared { +public: + /** + * Create a tracker with the given window size. + * @param window The maximum number of elements to take into account for calculation + * of max, mean and variance values. + */ + KisScalarTracker(const QString &name, int windowSize = 500) : + m_name(name), + m_windowSize(windowSize), + m_addCount(0), + m_max(windowSize), + m_acc(boost::accumulators::tag::rolling_window::window_size = windowSize) + { + m_printTimer.start(); + } + + virtual ~KisScalarTracker() { + } + + /** + * Add a scalar. + * @param value the scalar to be added. + */ + virtual void push(T value) { + m_max.push(value); + m_acc(value); + m_addCount++; + + if (m_addCount >= m_windowSize || m_printTimer.elapsed() >= 1000) { + m_printTimer.restart(); + QString s = format(boost::accumulators::rolling_mean(m_acc), + boost::accumulators::rolling_variance(m_acc), + m_max.max()); + print(s); + m_addCount = 0; + } + + } + +protected: + /** + * Print out a message. + * @param message the message to print + */ + virtual void print(const QString &message) { + qInfo() << qUtf8Printable(message); + } + + /** + * Formats a message for printing. + * @param mean the mean scalar in the window + * @param variance the variance of the scalar in the window + * @param max the max scalar in the window + */ + virtual QString format(qint64 mean, qint64 variance, qint64 max) { + return QString("%1: mean %2 ms, var %3, max %4 ms").arg(m_name).arg(mean).arg(variance).arg(max); + } + +private: + const QString m_name; + const int m_windowSize; + int m_addCount; + + QElapsedTimer m_printTimer; + + KisRollingMax m_max; + + // see https://svn.boost.org/trac10/ticket/11437 + typedef boost::accumulators::stats< + boost::accumulators::tag::lazy_rolling_mean, + boost::accumulators::tag::rolling_variance> stats; + + boost::accumulators::accumulator_set m_acc; +}; + +/** + * KisLatencyTracker tracks the time it takes events to reach a certain point in the program. + */ + +class KRITAGLOBAL_EXPORT KisLatencyTracker : public KisScalarTracker { +public: + /** + * Create a tracker with the given window size. + * @param window The maximum number of elements to take into account for calculation + * of max, mean and variance values. + */ + KisLatencyTracker(int windowSize = 500); + + /** + * Register that an event with the given timestamp has arrived just now. + * @param timestamp Timestamp of the event that just arrived (the difference to the + * current time is the latency). + */ + virtual void push(qint64 timestamp); + +protected: + /** + * @return The timestamp of "right now" in a frame that is comparable to those + * timestamps given to push(). + */ + virtual qint64 currentTimestamp() const = 0; +}; + +#endif //KRITA_KIS_SCALAR_TRACKER_H diff --git a/libs/global/kis_latency_tracker.cpp b/libs/global/kis_latency_tracker.cpp new file mode 100644 --- /dev/null +++ b/libs/global/kis_latency_tracker.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 Bernhard Liebl + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "kis_latency_tracker.h" + +KisLatencyTracker::KisLatencyTracker(int windowSize) : + KisScalarTracker("event latency", windowSize) +{ +} + +void KisLatencyTracker::push(qint64 timestamp) +{ + const qint64 latency = currentTimestamp() - timestamp; + KisScalarTracker::push(latency); +} diff --git a/libs/ui/input/kis_input_manager.cpp b/libs/ui/input/kis_input_manager.cpp --- a/libs/ui/input/kis_input_manager.cpp +++ b/libs/ui/input/kis_input_manager.cpp @@ -25,7 +25,7 @@ #include #include #include -#include +#include #include @@ -466,6 +466,10 @@ QTabletEvent *tabletEvent = static_cast(event); retval = compressMoveEventCommon(tabletEvent); + if (d->tabletLatencyTracker) { + d->tabletLatencyTracker->push(tabletEvent->timestamp()); + } + /** * The flow of tablet events means the tablet is in the * proximity area, so activate it even when the diff --git a/libs/ui/input/kis_input_manager_p.h b/libs/ui/input/kis_input_manager_p.h --- a/libs/ui/input/kis_input_manager_p.h +++ b/libs/ui/input/kis_input_manager_p.h @@ -24,6 +24,8 @@ #include #include #include +#include +#include #include "kis_input_manager.h" #include "kis_shortcut_matcher.h" @@ -34,7 +36,7 @@ #include "input/kis_tablet_debugger.h" #include "kis_timed_signal_threshold.h" #include "kis_signal_auto_connection.h" - +#include "kis_latency_tracker.h" class KisToolInvocationAction; @@ -146,4 +148,12 @@ bool containsPointer = true; int accumulatedScrollDelta = 0; + + class TabletLatencyTracker : public KisLatencyTracker { + protected: + virtual qint64 currentTimestamp() const; + virtual void print(const QString &message); + }; + + KisSharedPtr tabletLatencyTracker; }; diff --git a/libs/ui/input/kis_input_manager_p.cpp b/libs/ui/input/kis_input_manager_p.cpp --- a/libs/ui/input/kis_input_manager_p.cpp +++ b/libs/ui/input/kis_input_manager_p.cpp @@ -153,6 +153,10 @@ moveEventCompressor.setDelay(cfg.tabletEventsDelay()); testingAcceptCompressedTabletEvents = cfg.testingAcceptCompressedTabletEvents(); testingCompressBrushEvents = cfg.testingCompressBrushEvents(); + + if (cfg.trackTabletEventLatency()) { + tabletLatencyTracker = new TabletLatencyTracker(); + } } static const int InputWidgetsThreshold = 2000; @@ -568,3 +572,19 @@ return retval; } + +qint64 KisInputManager::Private::TabletLatencyTracker::currentTimestamp() const +{ + // on OS X, we need to compute the timestamp that compares correctly against the native event timestamp, + // which seems to be the msecs since system startup. On Linux with WinTab, we produce the timestamp that + // we compare against ourselves in QWindowSystemInterface. + + QElapsedTimer elapsed; + elapsed.start(); + return elapsed.msecsSinceReference(); +} + +void KisInputManager::Private::TabletLatencyTracker::print(const QString &message) +{ + dbgTablet << qUtf8Printable(message); +} diff --git a/libs/ui/input/wintab/qxcbconnection.cpp b/libs/ui/input/wintab/qxcbconnection.cpp --- a/libs/ui/input/wintab/qxcbconnection.cpp +++ b/libs/ui/input/wintab/qxcbconnection.cpp @@ -582,7 +582,7 @@ qreal tangentialPressure, qreal rotation, int z, qint64 uid, Qt::KeyboardModifiers modifiers) { - qint64 timestamp = g_eventTimer.elapsed(); + qint64 timestamp = g_eventTimer.msecsSinceReference() + g_eventTimer.elapsed(); QWindowSystemInterfacePrivate::TabletEvent *e = new QWindowSystemInterfacePrivate::TabletEvent(w, timestamp, local, global, device, pointerType, buttons, pressure, @@ -682,7 +682,7 @@ void QWindowSystemInterface::handleTabletEnterProximityEvent(int device, int pointerType, qint64 uid) { - qint64 timestamp = g_eventTimer.elapsed(); + qint64 timestamp = g_eventTimer.msecsSinceReference() + g_eventTimer.elapsed(); QTabletEvent ev(QEvent::TabletEnterProximity, QPointF(), QPointF(), device, pointerType, 0, 0, 0, @@ -694,7 +694,7 @@ void QWindowSystemInterface::handleTabletLeaveProximityEvent(int device, int pointerType, qint64 uid) { - qint64 timestamp = g_eventTimer.elapsed(); + qint64 timestamp = g_eventTimer.msecsSinceReference() + g_eventTimer.elapsed(); QTabletEvent ev(QEvent::TabletLeaveProximity, QPointF(), QPointF(), device, pointerType, 0, 0, 0, diff --git a/libs/ui/kis_config.h b/libs/ui/kis_config.h --- a/libs/ui/kis_config.h +++ b/libs/ui/kis_config.h @@ -453,6 +453,9 @@ int tabletEventsDelay(bool defaultValue = false) const; void setTabletEventsDelay(int value); + bool trackTabletEventLatency(bool defaultValue = false) const; + void setTrackTabletEventLatency(bool value); + bool testingAcceptCompressedTabletEvents(bool defaultValue = false) const; void setTestingAcceptCompressedTabletEvents(bool value); diff --git a/libs/ui/kis_config.cc b/libs/ui/kis_config.cc --- a/libs/ui/kis_config.cc +++ b/libs/ui/kis_config.cc @@ -1642,6 +1642,16 @@ m_cfg.writeEntry("tabletEventsDelay", value); } +bool KisConfig::trackTabletEventLatency(bool defaultValue) const +{ + return (defaultValue ? false : m_cfg.readEntry("trackTabletEventLatency", false)); +} + +void KisConfig::setTrackTabletEventLatency(bool value) +{ + m_cfg.writeEntry("trackTabletEventLatency", value); +} + bool KisConfig::testingAcceptCompressedTabletEvents(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("testingAcceptCompressedTabletEvents", false));