/* SPDX-FileCopyrightText: 2020 Vlad Zahorodnii SPDX-License-Identifier: GPL-2.0-or-later */ #include "renderloop.h" #include "options.h" #include "renderloop_p.h" #include "utils.h" namespace KWin { template T alignTimestamp(const T ×tamp, const T &alignment) { return timestamp + ((alignment - (timestamp % alignment)) % alignment); } RenderLoopPrivate *RenderLoopPrivate::get(RenderLoop *loop) { return loop->d.data(); } RenderLoopPrivate::RenderLoopPrivate(RenderLoop *q) : q(q) { compositeTimer.setSingleShot(true); QObject::connect(&compositeTimer, &QTimer::timeout, q, [this]() { dispatch(); }); } void RenderLoopPrivate::scheduleRepaint() { if (compositeTimer.isActive()) { return; } const std::chrono::nanoseconds currentTime(std::chrono::steady_clock::now().time_since_epoch()); const std::chrono::nanoseconds vblankInterval(1'000'000'000'000ull / refreshRate); // Estimate when the next presentation will occur. Note that this is a prediction. nextPresentationTimestamp = lastPresentationTimestamp + vblankInterval; if (nextPresentationTimestamp < currentTime) { nextPresentationTimestamp = lastPresentationTimestamp + alignTimestamp(currentTime - lastPresentationTimestamp, vblankInterval); } // Estimate when it's a good time to perform the next compositing cycle. const std::chrono::nanoseconds safetyMargin = std::chrono::milliseconds(3); std::chrono::nanoseconds renderTime; switch (options->latencyPolicy()) { case LatencyExteremelyLow: renderTime = std::chrono::nanoseconds(long(vblankInterval.count() * 0.1)); break; case LatencyLow: renderTime = std::chrono::nanoseconds(long(vblankInterval.count() * 0.25)); break; case LatencyMedium: renderTime = std::chrono::nanoseconds(long(vblankInterval.count() * 0.5)); break; case LatencyHigh: renderTime = std::chrono::nanoseconds(long(vblankInterval.count() * 0.75)); break; case LatencyExtremelyHigh: renderTime = std::chrono::nanoseconds(long(vblankInterval.count() * 0.9)); 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. if (nextRenderTimestamp < currentTime) { nextRenderTimestamp = currentTime; } const std::chrono::nanoseconds waitInterval = nextRenderTimestamp - currentTime; compositeTimer.start(std::chrono::duration_cast(waitInterval)); } void RenderLoopPrivate::delayScheduleRepaint() { pendingReschedule = true; } void RenderLoopPrivate::maybeScheduleRepaint() { if (pendingReschedule) { scheduleRepaint(); pendingReschedule = false; } } void RenderLoopPrivate::notifyFrameFailed() { Q_ASSERT(pendingFrameCount > 0); pendingFrameCount--; if (!inhibitCount) { maybeScheduleRepaint(); } } void RenderLoopPrivate::notifyFrameCompleted(std::chrono::nanoseconds timestamp) { Q_ASSERT(pendingFrameCount > 0); pendingFrameCount--; if (lastPresentationTimestamp <= timestamp) { lastPresentationTimestamp = timestamp; } else { qCWarning(KWIN_CORE, "Got invalid presentation timestamp: %ld (current %ld)", timestamp.count(), lastPresentationTimestamp.count()); lastPresentationTimestamp = std::chrono::steady_clock::now().time_since_epoch(); } if (!inhibitCount) { maybeScheduleRepaint(); } emit q->framePresented(q, timestamp); } void RenderLoopPrivate::dispatch() { // On X11, we want to ignore repaints that are scheduled by windows right before // the Compositor starts repainting. pendingRepaint = true; emit q->frameRequested(q); // The Compositor may decide to not repaint when the frameRequested() signal is // emitted, in which case the pending repaint flag has to be reset manually. pendingRepaint = false; } void RenderLoopPrivate::invalidate() { pendingReschedule = false; pendingFrameCount = 0; compositeTimer.stop(); } RenderLoop::RenderLoop(QObject *parent) : QObject(parent) , d(new RenderLoopPrivate(this)) { } RenderLoop::~RenderLoop() { } void RenderLoop::inhibit() { d->inhibitCount++; if (d->inhibitCount == 1) { d->compositeTimer.stop(); } } void RenderLoop::uninhibit() { Q_ASSERT(d->inhibitCount > 0); d->inhibitCount--; if (d->inhibitCount == 0) { d->maybeScheduleRepaint(); } } void RenderLoop::beginFrame() { d->pendingRepaint = false; d->pendingFrameCount++; d->renderJournal.beginFrame(); } void RenderLoop::endFrame() { d->renderJournal.endFrame(); } int RenderLoop::refreshRate() const { return d->refreshRate; } void RenderLoop::setRefreshRate(int refreshRate) { if (d->refreshRate == refreshRate) { return; } d->refreshRate = refreshRate; emit refreshRateChanged(); } void RenderLoop::scheduleRepaint() { if (d->pendingRepaint) { return; } if (!d->pendingFrameCount && !d->inhibitCount) { d->scheduleRepaint(); } else { d->delayScheduleRepaint(); } } std::chrono::nanoseconds RenderLoop::lastPresentationTimestamp() const { return d->lastPresentationTimestamp; } std::chrono::nanoseconds RenderLoop::nextPresentationTimestamp() const { return d->nextPresentationTimestamp; } } // namespace KWin