From 7f9cbbaa98c4cd633319e45e9a1100ea58f12840 Mon Sep 17 00:00:00 2001 From: Xaver Hugl Date: Wed, 20 Dec 2023 01:13:13 +0100 Subject: [PATCH] core/renderloop: improve frame scheduling heuristics with VRR Instead of checking for fullscreen windows and deciding whether or not to schedule repaints based on that, check if the active window is refreshing fast enough to be reasonable for vrr. For automatic mode, vrr is also enabled with the active window instead of the direct scanout candidate. BUG: 478680 BUG: 477199 --- src/compositor.cpp | 15 +++++++++------ src/core/output.h | 1 + src/core/renderloop.cpp | 16 ++++++++++------ src/core/renderloop.h | 7 +------ src/core/renderloop_p.h | 3 ++- src/scene/surfaceitem.cpp | 21 +++++++++++++++++++++ src/scene/surfaceitem.h | 7 +++++++ 7 files changed, 51 insertions(+), 19 deletions(-) diff --git a/src/compositor.cpp b/src/compositor.cpp index 6f17399cd5..83b72622a5 100644 --- a/src/compositor.cpp +++ b/src/compositor.cpp @@ -25,6 +25,7 @@ #include "utils/common.h" #include "wayland/surface.h" #include "wayland_server.h" +#include "window.h" #include "workspace.h" #include @@ -166,18 +167,20 @@ void Compositor::composite(RenderLoop *renderLoop) primaryLayer->resetRepaints(); prePaintPass(superLayer, &surfaceDamage); - SurfaceItem *scanoutCandidate = superLayer->delegate()->scanoutCandidate(); - renderLoop->setFullscreenSurface(scanoutCandidate); - frame->setContentType(scanoutCandidate ? scanoutCandidate->contentType() : ContentType::None); - const bool vrr = (output->capabilities() & Output::Capability::Vrr) && (output->vrrPolicy() == VrrPolicy::Always || (output->vrrPolicy() == VrrPolicy::Automatic && scanoutCandidate)); - const bool tearing = (output->capabilities() & Output::Capability::Tearing) && options->allowTearing() && scanoutCandidate && scanoutCandidate->presentationHint() == PresentationModeHint::Async; + Window *const activeWindow = workspace()->activeWindow(); + SurfaceItem *const activeFullscreenItem = activeWindow && activeWindow->isFullScreen() ? activeWindow->surfaceItem() : nullptr; + frame->setContentType(activeWindow && activeFullscreenItem ? activeFullscreenItem->contentType() : ContentType::None); + + const bool vrr = (output->capabilities() & Output::Capability::Vrr) && (output->vrrPolicy() == VrrPolicy::Always || (output->vrrPolicy() == VrrPolicy::Automatic && activeFullscreenItem)); + const bool tearing = (output->capabilities() & Output::Capability::Tearing) && options->allowTearing() && activeFullscreenItem && activeFullscreenItem->presentationHint() == PresentationModeHint::Async; if (vrr) { frame->setPresentationMode(tearing ? PresentationMode::AdaptiveAsync : PresentationMode::AdaptiveSync); } else { frame->setPresentationMode(tearing ? PresentationMode::Async : PresentationMode::VSync); } + bool directScanout = false; - if (scanoutCandidate) { + if (const auto scanoutCandidate = superLayer->delegate()->scanoutCandidate()) { const auto sublayers = superLayer->sublayers(); const bool scanoutPossible = std::none_of(sublayers.begin(), sublayers.end(), [](RenderLayer *sublayer) { return sublayer->isVisible(); diff --git a/src/core/output.h b/src/core/output.h index c84643e96b..2f027378ce 100644 --- a/src/core/output.h +++ b/src/core/output.h @@ -265,6 +265,7 @@ public: /** * Returns the RenderLoop for this output. If the platform does not support per screen * rendering, all outputs will share the same render loop. + * FIXME: remove this and decouple RenderLoop from Output */ virtual RenderLoop *renderLoop() const = 0; diff --git a/src/core/renderloop.cpp b/src/core/renderloop.cpp index 28e0e1115c..4dcd359b47 100644 --- a/src/core/renderloop.cpp +++ b/src/core/renderloop.cpp @@ -9,6 +9,8 @@ #include "renderloop_p.h" #include "scene/surfaceitem.h" #include "utils/common.h" +#include "window.h" +#include "workspace.h" namespace KWin { @@ -193,9 +195,16 @@ void RenderLoop::setRefreshRate(int refreshRate) void RenderLoop::scheduleRepaint(Item *item) { - if (d->pendingRepaint || (d->fullscreenItem != nullptr && item != nullptr && item != d->fullscreenItem)) { + if (d->pendingRepaint) { return; } + const bool vrr = d->presentationMode == PresentationMode::AdaptiveSync || d->presentationMode == PresentationMode::AdaptiveAsync; + if (vrr && workspace()->activeWindow()) { + Window *const activeWindow = workspace()->activeWindow(); + if (activeWindow->surfaceItem() && item != activeWindow->surfaceItem() && activeWindow->surfaceItem()->refreshRateEstimation() >= 30) { + return; + } + } if (!d->pendingFrameCount && !d->inhibitCount) { d->scheduleRepaint(); } else { @@ -213,11 +222,6 @@ std::chrono::nanoseconds RenderLoop::nextPresentationTimestamp() const return d->nextPresentationTimestamp; } -void RenderLoop::setFullscreenSurface(Item *surfaceItem) -{ - d->fullscreenItem = surfaceItem; -} - void RenderLoop::setPresentationMode(PresentationMode mode) { d->presentationMode = mode; diff --git a/src/core/renderloop.h b/src/core/renderloop.h index 21992a5381..caa9cb8249 100644 --- a/src/core/renderloop.h +++ b/src/core/renderloop.h @@ -14,6 +14,7 @@ namespace KWin { class RenderLoopPrivate; +class SurfaceItem; class Item; /** @@ -86,12 +87,6 @@ public: */ std::chrono::nanoseconds nextPresentationTimestamp() const; - /** - * Sets the surface that currently gets scanned out, - * so that this RenderLoop can adjust its timing behavior to that surface - */ - void setFullscreenSurface(Item *surface); - void setPresentationMode(PresentationMode mode); Q_SIGNALS: diff --git a/src/core/renderloop_p.h b/src/core/renderloop_p.h index cec1bfb0d4..c4779501b2 100644 --- a/src/core/renderloop_p.h +++ b/src/core/renderloop_p.h @@ -16,6 +16,8 @@ namespace KWin { +class SurfaceItem; + class KWIN_EXPORT RenderLoopPrivate { public: @@ -43,7 +45,6 @@ public: int inhibitCount = 0; bool pendingReschedule = false; bool pendingRepaint = false; - Item *fullscreenItem = nullptr; PresentationMode presentationMode = PresentationMode::VSync; }; diff --git a/src/scene/surfaceitem.cpp b/src/scene/surfaceitem.cpp index bbd1d50e09..856a06b029 100644 --- a/src/scene/surfaceitem.cpp +++ b/src/scene/surfaceitem.cpp @@ -81,6 +81,16 @@ static QRegion expandRegion(const QRegion ®ion, const QMargins &padding) void SurfaceItem::addDamage(const QRegion ®ion) { + if (m_lastDamage) { + const auto diff = std::chrono::steady_clock::now() - *m_lastDamage; + m_lastDamageTimeDiffs.push_back(diff); + if (m_lastDamageTimeDiffs.size() > 100) { + m_lastDamageTimeDiffs.pop_front(); + } + const auto average = std::accumulate(m_lastDamageTimeDiffs.begin(), m_lastDamageTimeDiffs.end(), std::chrono::nanoseconds::zero()) / m_lastDamageTimeDiffs.size(); + m_refreshRate = std::chrono::nanoseconds(1'000'000'000) / average; + } + m_lastDamage = std::chrono::steady_clock::now(); m_damage += region; const QRectF sourceBox = m_bufferTransform.map(m_bufferSourceBox, m_bufferSize); @@ -231,6 +241,17 @@ void SurfaceItem::freeze() { } +double SurfaceItem::refreshRateEstimation() const +{ + if (m_lastDamage) { + const auto diff = std::chrono::steady_clock::now() - *m_lastDamage; + const double refreshRate = std::chrono::nanoseconds(1'000'000'000) / diff; + return std::min(m_refreshRate, refreshRate); + } else { + return m_refreshRate; + } +} + SurfaceTexture::~SurfaceTexture() { } diff --git a/src/scene/surfaceitem.h b/src/scene/surfaceitem.h index ea89e8db5a..59519c8825 100644 --- a/src/scene/surfaceitem.h +++ b/src/scene/surfaceitem.h @@ -10,6 +10,8 @@ #include "core/output.h" #include "scene/item.h" +#include + namespace KWin { @@ -56,6 +58,8 @@ public: virtual void freeze(); + double refreshRateEstimation() const; + Q_SIGNALS: void damaged(); @@ -75,6 +79,9 @@ protected: QMatrix4x4 m_surfaceToBufferMatrix; QMatrix4x4 m_bufferToSurfaceMatrix; int m_referencePixmapCounter = 0; + std::deque m_lastDamageTimeDiffs; + std::optional m_lastDamage; + double m_refreshRate = 0; }; class KWIN_EXPORT SurfaceTexture