From ad5f8c5c595a59d391b7199654106762df6685f6 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Sat, 28 Nov 2020 17:12:38 +0200 Subject: [PATCH] Introduce RenderJournal Currently, we estimate the expected render time purely based on the latency policy. The problem with doing so is that the real render time might be larger, this can result in frame drops. In order to avoid frame drops, we need to take into account previous render times while estimating the next render time. For now, we just measure how long it takes to record rendering commands on the CPU. In the future, we might want consider using OpenGL timer queries for measuring the real render time, but for now, it's good enough. --- CMakeLists.txt | 1 + kwin.kcfg | 8 +++++++ options.cpp | 16 +++++++++++++ options.h | 18 +++++++++++++++ renderjournal.cpp | 56 ++++++++++++++++++++++++++++++++++++++++++++++ renderjournal.h | 57 +++++++++++++++++++++++++++++++++++++++++++++++ renderloop.cpp | 14 ++++++++++++ renderloop_p.h | 2 ++ 8 files changed, 172 insertions(+) create mode 100644 renderjournal.cpp create mode 100644 renderjournal.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f08c1b39e2..399334424a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -506,6 +506,7 @@ set(kwin_SRCS pluginmanager.cpp pointer_input.cpp popup_input_filter.cpp + renderjournal.cpp renderloop.cpp rootinfo_filter.cpp rulebooksettings.cpp diff --git a/kwin.kcfg b/kwin.kcfg index ea03032d23..4a0effc51c 100644 --- a/kwin.kcfg +++ b/kwin.kcfg @@ -255,6 +255,14 @@ LatencyMedium + + + + + + + RenderTimeEstimatorMaximum + diff --git a/options.cpp b/options.cpp index 0ed1ad765a..31465c59a5 100644 --- a/options.cpp +++ b/options.cpp @@ -54,6 +54,7 @@ Options::Options(QObject *parent) , m_xwaylandCrashPolicy(Options::defaultXwaylandCrashPolicy()) , m_xwaylandMaxCrashCount(Options::defaultXwaylandMaxCrashCount()) , m_latencyPolicy(Options::defaultLatencyPolicy()) + , m_renderTimeEstimator(Options::defaultRenderTimeEstimator()) , m_compositingMode(Options::defaultCompositingMode()) , m_useCompositing(Options::defaultUseCompositing()) , m_hiddenPreviews(Options::defaultHiddenPreviews()) @@ -648,6 +649,20 @@ void Options::setLatencyPolicy(LatencyPolicy policy) emit latencyPolicyChanged(); } +RenderTimeEstimator Options::renderTimeEstimator() const +{ + return m_renderTimeEstimator; +} + +void Options::setRenderTimeEstimator(RenderTimeEstimator estimator) +{ + if (m_renderTimeEstimator == estimator) { + return; + } + m_renderTimeEstimator = estimator; + emit renderTimeEstimatorChanged(); +} + void Options::setGlPlatformInterface(OpenGLPlatformInterface interface) { // check environment variable @@ -796,6 +811,7 @@ void Options::syncFromKcfgc() setWindowsBlockCompositing(m_settings->windowsBlockCompositing()); setMoveMinimizedWindowsToEndOfTabBoxFocusChain(m_settings->moveMinimizedWindowsToEndOfTabBoxFocusChain()); setLatencyPolicy(m_settings->latencyPolicy()); + setRenderTimeEstimator(m_settings->renderTimeEstimator()); } bool Options::loadCompositingConfig (bool force) diff --git a/options.h b/options.h index 041848f468..13c4da7ec1 100644 --- a/options.h +++ b/options.h @@ -53,6 +53,15 @@ enum LatencyPolicy { LatencyExtremelyHigh, }; +/** + * This enum type specifies the method for estimating the expected render time. + */ +enum RenderTimeEstimator { + RenderTimeEstimatorMinimum, + RenderTimeEstimatorMaximum, + RenderTimeEstimatorAverage, +}; + class Settings; class KWIN_EXPORT Options : public QObject @@ -60,6 +69,7 @@ class KWIN_EXPORT Options : public QObject Q_OBJECT Q_ENUMS(XwaylandCrashPolicy) Q_ENUMS(LatencyPolicy) + Q_ENUMS(RenderTimeEstimator) Q_PROPERTY(FocusPolicy focusPolicy READ focusPolicy WRITE setFocusPolicy NOTIFY focusPolicyChanged) Q_PROPERTY(XwaylandCrashPolicy xwaylandCrashPolicy READ xwaylandCrashPolicy WRITE setXwaylandCrashPolicy NOTIFY xwaylandCrashPolicyChanged) Q_PROPERTY(int xwaylandMaxCrashCount READ xwaylandMaxCrashCount WRITE setXwaylandMaxCrashCount NOTIFY xwaylandMaxCrashCountChanged) @@ -188,6 +198,7 @@ class KWIN_EXPORT Options : public QObject Q_PROPERTY(KWin::OpenGLPlatformInterface glPlatformInterface READ glPlatformInterface WRITE setGlPlatformInterface NOTIFY glPlatformInterfaceChanged) Q_PROPERTY(bool windowsBlockCompositing READ windowsBlockCompositing WRITE setWindowsBlockCompositing NOTIFY windowsBlockCompositingChanged) Q_PROPERTY(LatencyPolicy latencyPolicy READ latencyPolicy WRITE setLatencyPolicy NOTIFY latencyPolicyChanged) + Q_PROPERTY(RenderTimeEstimator renderTimeEstimator READ renderTimeEstimator WRITE setRenderTimeEstimator NOTIFY renderTimeEstimatorChanged) public: explicit Options(QObject *parent = nullptr); @@ -596,6 +607,7 @@ public: QStringList modifierOnlyDBusShortcut(Qt::KeyboardModifier mod) const; LatencyPolicy latencyPolicy() const; + RenderTimeEstimator renderTimeEstimator() const; // setters void setFocusPolicy(FocusPolicy focusPolicy); @@ -655,6 +667,7 @@ public: void setWindowsBlockCompositing(bool set); void setMoveMinimizedWindowsToEndOfTabBoxFocusChain(bool set); void setLatencyPolicy(LatencyPolicy policy); + void setRenderTimeEstimator(RenderTimeEstimator estimator); // default values static WindowOperation defaultOperationTitlebarDblClick() { @@ -756,6 +769,9 @@ public: static LatencyPolicy defaultLatencyPolicy() { return LatencyMedium; } + static RenderTimeEstimator defaultRenderTimeEstimator() { + return RenderTimeEstimatorMaximum; + } /** * Performs loading all settings except compositing related. */ @@ -828,6 +844,7 @@ Q_SIGNALS: void animationSpeedChanged(); void latencyPolicyChanged(); void configChanged(); + void renderTimeEstimatorChanged(); private: void setElectricBorders(int borders); @@ -856,6 +873,7 @@ private: XwaylandCrashPolicy m_xwaylandCrashPolicy; int m_xwaylandMaxCrashCount; LatencyPolicy m_latencyPolicy; + RenderTimeEstimator m_renderTimeEstimator; CompositingType m_compositingMode; bool m_useCompositing; diff --git a/renderjournal.cpp b/renderjournal.cpp new file mode 100644 index 0000000000..1189e88e45 --- /dev/null +++ b/renderjournal.cpp @@ -0,0 +1,56 @@ +/* + SPDX-FileCopyrightText: 2020 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "renderjournal.h" + +namespace KWin +{ + +RenderJournal::RenderJournal() +{ +} + +void RenderJournal::beginFrame() +{ + m_timer.start(); +} + +void RenderJournal::endFrame() +{ + std::chrono::nanoseconds duration(m_timer.nsecsElapsed()); + if (m_log.count() >= m_size) { + m_log.dequeue(); + } + m_log.enqueue(duration); +} + +std::chrono::nanoseconds RenderJournal::minimum() const +{ + auto it = std::min_element(m_log.constBegin(), m_log.constEnd()); + return it != m_log.constEnd() ? (*it) : std::chrono::nanoseconds::zero(); +} + +std::chrono::nanoseconds RenderJournal::maximum() const +{ + auto it = std::max_element(m_log.constBegin(), m_log.constEnd()); + return it != m_log.constEnd() ? (*it) : std::chrono::nanoseconds::zero(); +} + +std::chrono::nanoseconds RenderJournal::average() const +{ + if (m_log.isEmpty()) { + return std::chrono::nanoseconds::zero(); + } + + std::chrono::nanoseconds result = std::chrono::nanoseconds::zero(); + for (const std::chrono::nanoseconds &entry : m_log) { + result += entry; + } + + return result / m_log.count(); +} + +} // namespace KWin diff --git a/renderjournal.h b/renderjournal.h new file mode 100644 index 0000000000..dcd73ad347 --- /dev/null +++ b/renderjournal.h @@ -0,0 +1,57 @@ +/* + SPDX-FileCopyrightText: 2020 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include "kwinglobals.h" + +#include +#include + +namespace KWin +{ + +/** + * The RenderJournal class measures how long it takes to render frames and estimates how + * long it will take to render the next frame. + */ +class KWIN_EXPORT RenderJournal +{ +public: + RenderJournal(); + + /** + * This function must be called before starting rendering a new frame. + */ + void beginFrame(); + + /** + * This function must be called after finishing rendering a frame. + */ + void endFrame(); + + /** + * Returns the maximum estimated amount of time that it takes to render a single frame. + */ + std::chrono::nanoseconds maximum() const; + + /** + * Returns the minimum estimated amount of time that it takes to render a single frame. + */ + std::chrono::nanoseconds minimum() const; + + /** + * Returns the average estimated amount of time that it takes to render a single frame. + */ + std::chrono::nanoseconds average() const; + +private: + QElapsedTimer m_timer; + QQueue m_log; + int m_size = 15; +}; + +} // namespace KWin diff --git a/renderloop.cpp b/renderloop.cpp index f6e90d7939..5b836a144f 100644 --- a/renderloop.cpp +++ b/renderloop.cpp @@ -65,6 +65,18 @@ void RenderLoopPrivate::scheduleRepaint() break; } + switch (options->renderTimeEstimator()) { + case RenderTimeEstimatorMinimum: + renderTime = std::max(renderTime, renderJournal.minimum()); + break; + case RenderTimeEstimatorMaximum: + renderTime = std::max(renderTime, renderJournal.maximum()); + break; + case RenderTimeEstimatorAverage: + renderTime = std::max(renderTime, renderJournal.average()); + break; + } + std::chrono::nanoseconds nextRenderTimestamp = nextPresentationTimestamp - renderTime - safetyMargin; // If we can't render the frame before the deadline, start compositing immediately. @@ -162,10 +174,12 @@ void RenderLoop::beginFrame() { d->pendingRepaint = false; d->pendingFrameCount++; + d->renderJournal.beginFrame(); } void RenderLoop::endFrame() { + d->renderJournal.endFrame(); } int RenderLoop::refreshRate() const diff --git a/renderloop_p.h b/renderloop_p.h index ffb0a6f4fd..e26552ebfa 100644 --- a/renderloop_p.h +++ b/renderloop_p.h @@ -7,6 +7,7 @@ #pragma once #include "renderloop.h" +#include "renderjournal.h" #include @@ -32,6 +33,7 @@ public: std::chrono::nanoseconds lastPresentationTimestamp = std::chrono::nanoseconds::zero(); std::chrono::nanoseconds nextPresentationTimestamp = std::chrono::nanoseconds::zero(); QTimer compositeTimer; + RenderJournal renderJournal; int refreshRate = 60000; int pendingFrameCount = 0; int inhibitCount = 0;