diff options
Diffstat (limited to 'src/monitor-lib/frametimer.cpp')
-rw-r--r-- | src/monitor-lib/frametimer.cpp | 290 |
1 files changed, 284 insertions, 6 deletions
diff --git a/src/monitor-lib/frametimer.cpp b/src/monitor-lib/frametimer.cpp index ef483736..534c3033 100644 --- a/src/monitor-lib/frametimer.cpp +++ b/src/monitor-lib/frametimer.cpp @@ -41,12 +41,75 @@ #include "frametimer.h" +#include <QQuickWindow> +#include <qqmlinfo.h> + +#include "waylandwindow.h" +#include "inprocesswindow.h" + +/*! + \qmltype FrameTimer + \inqmlmodule QtApplicationManager + \ingroup common-instantiatable + \brief Provides frame-rate information about a given window. + + FrameTimer is used to get frame-rate information for a given window. The window can be either + a toplevel Window (from the QtQuick.Window module) or a WindowObject + (from the QtApplicationManager.SystemUI module). + + The following snippet shows how to use FrameTimer to display the frame-rate of a Window: + + \qml + import QtQuick 2.11 + import QtApplicationManager 1.0 + + Window { + id: toplevelWindow + ... + FrameTimer { + id: frameTimer + running: topLevelWindow.visible + window: toplevelWindow + } + Text { + text: "FPS: " + Number(frameTimer.averageFps).toLocaleString(Qt.locale("en_US"), 'f', 1) + } + } + \endqml + + You can also use this component as a MonitorModel data source if you want to plot its + previous values over time: + + \qml + import QtQuick 2.11 + import QtApplicationManager 1.0 + + Window { + id: toplevelWindow + ... + MonitorModel { + running: true + FrameTimer { + window: toplevelWindow + } + } + } + \endqml + + Please note that when using FrameTimer as a MonitorModel data source there's no need to set it + to \l{FrameTimer::running}{running} as MonitorModel will already call update() as needed. +*/ + QT_BEGIN_NAMESPACE_AM const qreal FrameTimer::MicrosInSec = qreal(1000 * 1000); -FrameTimer::FrameTimer() -{ } +FrameTimer::FrameTimer(QObject *parent) + : QObject(parent) +{ + m_updateTimer.setInterval(1000); + connect(&m_updateTimer, &QTimer::timeout, this, &FrameTimer::update); +} void FrameTimer::newFrame() { @@ -67,24 +130,239 @@ void FrameTimer::reset() m_min = std::numeric_limits<int>::max(); } +/*! + \qmlproperty real FrameTimer::averageFps + \readonly + + The average frame rate of the given \l window, in frames per second, since update() + was last called (either manually or automatically in case \l{FrameTimer::running}{running} is set to true). + + \sa window running update() +*/ qreal FrameTimer::averageFps() const { - return m_sum ? MicrosInSec * m_count / m_sum : qreal(0); + return m_averageFps; } +/*! + \qmlproperty real FrameTimer::minimumFps + \readonly + + The minimum frame rate of the given \l window, in frames per second, since update() + was last called (either manually or automatically in case \l{FrameTimer::running}{running} is set to true). + + \sa window running update() +*/ qreal FrameTimer::minimumFps() const { - return m_max ? MicrosInSec / m_max : qreal(0); + return m_minimumFps; } +/*! + \qmlproperty real FrameTimer::maximumFps + \readonly + + The maximum frame rate of the given \l window, in frames per second, since update() + was last called (either manually or automatically in case \l{FrameTimer::running}{running} is set to true). + + \sa window running update() +*/ qreal FrameTimer::maximumFps() const { - return m_min ? MicrosInSec / m_min : qreal(0); + return m_maximumFps; } +/*! + \qmlproperty real FrameTimer::jitterFps + \readonly + + The frame rate jitter of the given \l window, in frames per second, since update() + was last called (either manually or automatically in case \l{FrameTimer::running}{running} is set to true). + + \sa window running update() +*/ qreal FrameTimer::jitterFps() const { - return m_count ? m_jitter / m_count : qreal(0); + return m_jitterFps; +} + +/*! + \qmlproperty Object FrameTimer::window + + The window to be monitored, from which frame-rate information will be gathered. + It can be either a toplevel Window (from the QtQuick.Window module) or a WindowObject + (from the QtApplicationManager.SystemUI module). + + \sa WindowObject +*/ +QObject *FrameTimer::window() const +{ + return m_window; +} + +void FrameTimer::setWindow(QObject *value) +{ + if (m_window == value) + return; + +#if defined(AM_MULTI_PROCESS) + disconnectFromWaylandSurface(); +#endif + + if (m_window) + disconnect(m_window, nullptr, this, nullptr); + + m_window = value; + + if (m_window) { + if (!connectToQuickWindow() && !connectToAppManWindow()) + qmlWarning(this) << "The given window is neither a QQuickWindow nor a WindowObject."; + } + + emit windowChanged(); +} + +bool FrameTimer::connectToQuickWindow() +{ + QQuickWindow *quickWindow = qobject_cast<QQuickWindow*>(m_window); + if (!quickWindow) + return false; + + connect(quickWindow, &QQuickWindow::frameSwapped, this, &FrameTimer::newFrame, Qt::UniqueConnection); + return true; +} + +bool FrameTimer::connectToAppManWindow() +{ + Window *appManWindow = qobject_cast<Window*>(m_window); + if (!appManWindow) + return false; + + if (qobject_cast<InProcessWindow*>(appManWindow)) { + qmlWarning(this) << "It makes no sense to measure the FPS of a WindowObject in single-process mode." + " FrameTimer won't operate with the given window."; + return true; + } + +#if defined(AM_MULTI_PROCESS) + WaylandWindow *waylandWindow = qobject_cast<WaylandWindow*>(m_window); + Q_ASSERT(waylandWindow); + + connect(waylandWindow, &WaylandWindow::waylandSurfaceChanged, + this, &FrameTimer::connectToWaylandSurface, Qt::UniqueConnection); + + connectToWaylandSurface(); +#endif + + return true; +} + +#if defined(AM_MULTI_PROCESS) +void FrameTimer::connectToWaylandSurface() +{ + WaylandWindow *waylandWindow = qobject_cast<WaylandWindow*>(m_window); + Q_ASSERT(waylandWindow); + + disconnectFromWaylandSurface(); + + m_waylandSurface = waylandWindow->waylandSurface(); + if (m_waylandSurface) + connect(m_waylandSurface, &QWaylandQuickSurface::redraw, this, &FrameTimer::newFrame, Qt::UniqueConnection); +} + +void FrameTimer::disconnectFromWaylandSurface() +{ + if (!m_waylandSurface) + return; + + disconnect(m_waylandSurface, nullptr, this, nullptr); + + m_waylandSurface = nullptr; +} +#endif + +/*! + \qmlproperty list<string> FrameTimer::roleNames + \readonly + + Names of the roles provided by FrameTimer when used as a MonitorModel data source. + + \sa MonitorModel +*/ +QStringList FrameTimer::roleNames() const +{ + return { qSL("averageFps"), qSL("minimumFps"), qSL("maximumFps"), qSL("jitterFps") }; +} + +/*! + \qmlmethod FrameTimer::update + + Updates the properties averageFps, minimumFps, maximumFps and jitterFps. Then resets internal + counters so that new numbers can be taken for the new time period starting from the moment + this method is called. + + Note that you normally don't have to call this method directly, as FrameTimer does it automatically + every \l interval milliseconds while \l{FrameTimer::running}{running} is set to true. + + \sa running +*/ +void FrameTimer::update() +{ + m_averageFps = m_sum ? MicrosInSec * m_count / m_sum : qreal(0); + m_minimumFps = m_max ? MicrosInSec / m_max : qreal(0); + m_maximumFps = m_min ? MicrosInSec / m_min : qreal(0); + m_jitterFps = m_count ? m_jitter / m_count : qreal(0); + + // Start counting again for the next sampling period but keep m_timer running because + // we still need the diff between the last rendered frame and the upcoming one. + reset(); + + emit updated(); +} + +/*! + \qmlproperty bool FrameTimer::running + + If \c true, update() will get called automatically every \l interval milliseconds. + + When using FrameTimer as a MonitorModel data source, this property should be kept as \c false. + + \sa update() interval +*/ +bool FrameTimer::running() const +{ + return m_updateTimer.isActive(); +} + +void FrameTimer::setRunning(bool value) +{ + if (value && !m_updateTimer.isActive()) { + m_updateTimer.start(); + emit runningChanged(); + } else if (!value && m_updateTimer.isActive()) { + m_updateTimer.stop(); + emit runningChanged(); + } +} + +/*! + \qmlproperty int FrameTimer::interval + + The interval, in milliseconds, between update() calls while \l{FrameTimer::running}{running} is \c true. + + \sa update() running +*/ +int FrameTimer::interval() const +{ + return m_updateTimer.interval(); +} + +void FrameTimer::setInterval(int value) +{ + if (value != m_updateTimer.interval()) { + m_updateTimer.setInterval(value); + emit intervalChanged(); + } } QT_END_NAMESPACE_AM |