Compare commits

...

4 Commits

Author SHA1 Message Date
Xaver Hugl 192232e8d1 backends/drm: allow up to two composited frames to be pending at the same time
This should improve responsiveness on setups where rendering each frame takes longer
than the refresh cycle of the display.

BUG: 452119
CCBUG: 454098
2024-05-24 17:59:32 +02:00
Xaver Hugl 71628fa9c1 core/renderloop: support triple buffering
When rendering a frame takes longer than a refresh period, allow rendering to
happen before the previous frame is presented. This way the effective refresh
rate is increased, without increasing latency or impacting frame pacing more
than necessary
2024-05-24 17:59:32 +02:00
Xaver Hugl 2ab11fcb29 backends/drm: store OutputFrames in the commit objects
That way, multiple frames in flight can be tracked correctly
2024-05-24 17:59:32 +02:00
Xaver Hugl c3b43f26fb backends: move output refresh duration to compositor 2024-05-24 17:59:32 +02:00
36 changed files with 236 additions and 222 deletions

View File

@ -384,7 +384,7 @@ void DrmTest::testModeset()
layer->beginFrame(); layer->beginFrame();
output->renderLoop()->prepareNewFrame(); output->renderLoop()->prepareNewFrame();
output->renderLoop()->beginPaint(); output->renderLoop()->beginPaint();
const auto frame = std::make_shared<OutputFrame>(output->renderLoop()); const auto frame = std::make_shared<OutputFrame>(output->renderLoop(), std::chrono::nanoseconds(1'000'000'000'000 / output->refreshRate()));
layer->endFrame(infiniteRegion(), infiniteRegion(), frame.get()); layer->endFrame(infiniteRegion(), infiniteRegion(), frame.get());
QVERIFY(output->present(frame)); QVERIFY(output->present(frame));

View File

@ -27,17 +27,6 @@ RenderLoop *DrmAbstractOutput::renderLoop() const
return m_renderLoop.get(); return m_renderLoop.get();
} }
void DrmAbstractOutput::frameFailed() const
{
m_frame->failed();
}
void DrmAbstractOutput::pageFlipped(std::chrono::nanoseconds timestamp, PresentationMode mode)
{
m_frame->presented(std::chrono::nanoseconds(1'000'000'000'000 / refreshRate()), timestamp, mode);
m_frame.reset();
}
DrmGpu *DrmAbstractOutput::gpu() const DrmGpu *DrmAbstractOutput::gpu() const
{ {
return m_gpu; return m_gpu;

View File

@ -25,8 +25,6 @@ public:
DrmAbstractOutput(DrmGpu *gpu); DrmAbstractOutput(DrmGpu *gpu);
RenderLoop *renderLoop() const override; RenderLoop *renderLoop() const override;
void frameFailed() const;
void pageFlipped(std::chrono::nanoseconds timestamp, PresentationMode mode);
DrmGpu *gpu() const; DrmGpu *gpu() const;
virtual bool present(const std::shared_ptr<OutputFrame> &frame) = 0; virtual bool present(const std::shared_ptr<OutputFrame> &frame) = 0;
@ -39,7 +37,6 @@ protected:
friend class DrmGpu; friend class DrmGpu;
std::unique_ptr<RenderLoop> m_renderLoop; std::unique_ptr<RenderLoop> m_renderLoop;
std::shared_ptr<OutputFrame> m_frame;
DrmGpu *const m_gpu; DrmGpu *const m_gpu;
}; };

View File

@ -7,6 +7,7 @@
SPDX-License-Identifier: GPL-2.0-or-later SPDX-License-Identifier: GPL-2.0-or-later
*/ */
#include "drm_commit.h" #include "drm_commit.h"
#include "core/renderbackend.h"
#include "drm_blob.h" #include "drm_blob.h"
#include "drm_buffer.h" #include "drm_buffer.h"
#include "drm_connector.h" #include "drm_connector.h"
@ -54,10 +55,11 @@ void DrmAtomicCommit::addBlob(const DrmProperty &prop, const std::shared_ptr<Drm
m_blobs[&prop] = blob; m_blobs[&prop] = blob;
} }
void DrmAtomicCommit::addBuffer(DrmPlane *plane, const std::shared_ptr<DrmFramebuffer> &buffer) void DrmAtomicCommit::addBuffer(DrmPlane *plane, const std::shared_ptr<DrmFramebuffer> &buffer, const std::shared_ptr<OutputFrame> &frame)
{ {
addProperty(plane->fbId, buffer ? buffer->framebufferId() : 0); addProperty(plane->fbId, buffer ? buffer->framebufferId() : 0);
m_buffers[plane] = buffer; m_buffers[plane] = buffer;
m_frames[plane] = frame;
// atomic commits with IN_FENCE_FD fail with NVidia // atomic commits with IN_FENCE_FD fail with NVidia
if (plane->inFenceFd.isValid() && !plane->gpu()->isNVidia()) { if (plane->inFenceFd.isValid() && !plane->gpu()->isNVidia()) {
addProperty(plane->inFenceFd, buffer ? buffer->syncFd().get() : -1); addProperty(plane->inFenceFd, buffer ? buffer->syncFd().get() : -1);
@ -132,20 +134,20 @@ bool DrmAtomicCommit::doCommit(uint32_t flags)
return drmIoctl(m_gpu->fd(), DRM_IOCTL_MODE_ATOMIC, &commitData) == 0; return drmIoctl(m_gpu->fd(), DRM_IOCTL_MODE_ATOMIC, &commitData) == 0;
} }
void DrmAtomicCommit::pageFlipped(std::chrono::nanoseconds timestamp) const void DrmAtomicCommit::pageFlipped(std::chrono::nanoseconds timestamp)
{ {
Q_ASSERT(QThread::currentThread() == QApplication::instance()->thread()); Q_ASSERT(QThread::currentThread() == QApplication::instance()->thread());
for (const auto &[plane, buffer] : m_buffers) { for (const auto &[plane, buffer] : m_buffers) {
plane->setCurrentBuffer(buffer); plane->setCurrentBuffer(buffer);
} }
DrmPipeline::PageflipType type = DrmPipeline::PageflipType::Normal; for (const auto &[plane, frame] : m_frames) {
if (m_modeset) { if (frame) {
type = DrmPipeline::PageflipType::Modeset; frame->presented(timestamp, m_mode);
} else if (m_cursorOnly) { }
type = DrmPipeline::PageflipType::CursorOnly;
} }
m_frames.clear();
for (const auto pipeline : std::as_const(m_pipelines)) { for (const auto pipeline : std::as_const(m_pipelines)) {
pipeline->pageFlipped(timestamp, type, m_mode); pipeline->pageFlipped(timestamp);
} }
} }
@ -186,6 +188,7 @@ void DrmAtomicCommit::merge(DrmAtomicCommit *onTop)
} }
for (const auto &[plane, buffer] : onTop->m_buffers) { for (const auto &[plane, buffer] : onTop->m_buffers) {
m_buffers[plane] = buffer; m_buffers[plane] = buffer;
m_frames[plane] = onTop->m_frames[plane];
m_planes.emplace(plane); m_planes.emplace(plane);
} }
for (const auto &[prop, blob] : onTop->m_blobs) { for (const auto &[prop, blob] : onTop->m_blobs) {
@ -207,16 +210,16 @@ bool DrmAtomicCommit::isCursorOnly() const
return m_cursorOnly; return m_cursorOnly;
} }
DrmLegacyCommit::DrmLegacyCommit(DrmPipeline *pipeline, const std::shared_ptr<DrmFramebuffer> &buffer) DrmLegacyCommit::DrmLegacyCommit(DrmPipeline *pipeline, const std::shared_ptr<DrmFramebuffer> &buffer, const std::shared_ptr<OutputFrame> &frame)
: DrmCommit(pipeline->gpu()) : DrmCommit(pipeline->gpu())
, m_pipeline(pipeline) , m_pipeline(pipeline)
, m_buffer(buffer) , m_buffer(buffer)
, m_frame(frame)
{ {
} }
bool DrmLegacyCommit::doModeset(DrmConnector *connector, DrmConnectorMode *mode) bool DrmLegacyCommit::doModeset(DrmConnector *connector, DrmConnectorMode *mode)
{ {
m_modeset = true;
uint32_t connectorId = connector->id(); uint32_t connectorId = connector->id();
if (drmModeSetCrtc(gpu()->fd(), m_pipeline->crtc()->id(), m_buffer->framebufferId(), 0, 0, &connectorId, 1, mode->nativeMode()) == 0) { if (drmModeSetCrtc(gpu()->fd(), m_pipeline->crtc()->id(), m_buffer->framebufferId(), 0, 0, &connectorId, 1, mode->nativeMode()) == 0) {
m_pipeline->crtc()->setCurrent(m_buffer); m_pipeline->crtc()->setCurrent(m_buffer);
@ -236,10 +239,14 @@ bool DrmLegacyCommit::doPageflip(PresentationMode mode)
return drmModePageFlip(gpu()->fd(), m_pipeline->crtc()->id(), m_buffer->framebufferId(), flags, this) == 0; return drmModePageFlip(gpu()->fd(), m_pipeline->crtc()->id(), m_buffer->framebufferId(), flags, this) == 0;
} }
void DrmLegacyCommit::pageFlipped(std::chrono::nanoseconds timestamp) const void DrmLegacyCommit::pageFlipped(std::chrono::nanoseconds timestamp)
{ {
Q_ASSERT(QThread::currentThread() == QApplication::instance()->thread()); Q_ASSERT(QThread::currentThread() == QApplication::instance()->thread());
m_pipeline->crtc()->setCurrent(m_buffer); m_pipeline->crtc()->setCurrent(m_buffer);
m_pipeline->pageFlipped(timestamp, m_modeset ? DrmPipeline::PageflipType::Modeset : DrmPipeline::PageflipType::Normal, m_mode); if (m_frame) {
m_frame->presented(timestamp, m_mode);
m_frame.reset();
}
m_pipeline->pageFlipped(timestamp);
} }
} }

View File

@ -32,6 +32,7 @@ class DrmGpu;
class DrmPlane; class DrmPlane;
class DrmProperty; class DrmProperty;
class DrmPipeline; class DrmPipeline;
class OutputFrame;
class DrmCommit class DrmCommit
{ {
@ -39,7 +40,7 @@ public:
virtual ~DrmCommit(); virtual ~DrmCommit();
DrmGpu *gpu() const; DrmGpu *gpu() const;
virtual void pageFlipped(std::chrono::nanoseconds timestamp) const = 0; virtual void pageFlipped(std::chrono::nanoseconds timestamp) = 0;
protected: protected:
DrmCommit(DrmGpu *gpu); DrmCommit(DrmGpu *gpu);
@ -60,7 +61,7 @@ public:
addProperty(prop, prop.valueForEnum(enumValue)); addProperty(prop, prop.valueForEnum(enumValue));
} }
void addBlob(const DrmProperty &prop, const std::shared_ptr<DrmBlob> &blob); void addBlob(const DrmProperty &prop, const std::shared_ptr<DrmBlob> &blob);
void addBuffer(DrmPlane *plane, const std::shared_ptr<DrmFramebuffer> &buffer); void addBuffer(DrmPlane *plane, const std::shared_ptr<DrmFramebuffer> &buffer, const std::shared_ptr<OutputFrame> &frame);
void setVrr(DrmCrtc *crtc, bool vrr); void setVrr(DrmCrtc *crtc, bool vrr);
void setPresentationMode(PresentationMode mode); void setPresentationMode(PresentationMode mode);
@ -69,7 +70,7 @@ public:
bool commit(); bool commit();
bool commitModeset(); bool commitModeset();
void pageFlipped(std::chrono::nanoseconds timestamp) const override; void pageFlipped(std::chrono::nanoseconds timestamp) override;
bool areBuffersReadable() const; bool areBuffersReadable() const;
void setDeadline(std::chrono::steady_clock::time_point deadline); void setDeadline(std::chrono::steady_clock::time_point deadline);
@ -87,6 +88,7 @@ private:
const QList<DrmPipeline *> m_pipelines; const QList<DrmPipeline *> m_pipelines;
std::unordered_map<const DrmProperty *, std::shared_ptr<DrmBlob>> m_blobs; std::unordered_map<const DrmProperty *, std::shared_ptr<DrmBlob>> m_blobs;
std::unordered_map<DrmPlane *, std::shared_ptr<DrmFramebuffer>> m_buffers; std::unordered_map<DrmPlane *, std::shared_ptr<DrmFramebuffer>> m_buffers;
std::unordered_map<DrmPlane *, std::shared_ptr<OutputFrame>> m_frames;
std::unordered_set<DrmPlane *> m_planes; std::unordered_set<DrmPlane *> m_planes;
std::optional<bool> m_vrr; std::optional<bool> m_vrr;
std::unordered_map<uint32_t /* object */, std::unordered_map<uint32_t /* property */, uint64_t /* value */>> m_properties; std::unordered_map<uint32_t /* object */, std::unordered_map<uint32_t /* property */, uint64_t /* value */>> m_properties;
@ -98,16 +100,16 @@ private:
class DrmLegacyCommit : public DrmCommit class DrmLegacyCommit : public DrmCommit
{ {
public: public:
DrmLegacyCommit(DrmPipeline *pipeline, const std::shared_ptr<DrmFramebuffer> &buffer); DrmLegacyCommit(DrmPipeline *pipeline, const std::shared_ptr<DrmFramebuffer> &buffer, const std::shared_ptr<OutputFrame> &frame);
bool doModeset(DrmConnector *connector, DrmConnectorMode *mode); bool doModeset(DrmConnector *connector, DrmConnectorMode *mode);
bool doPageflip(PresentationMode mode); bool doPageflip(PresentationMode mode);
void pageFlipped(std::chrono::nanoseconds timestamp) const override; void pageFlipped(std::chrono::nanoseconds timestamp) override;
private: private:
DrmPipeline *const m_pipeline; DrmPipeline *const m_pipeline;
const std::shared_ptr<DrmFramebuffer> m_buffer; const std::shared_ptr<DrmFramebuffer> m_buffer;
bool m_modeset = false; std::shared_ptr<OutputFrame> m_frame;
PresentationMode m_mode = PresentationMode::VSync; PresentationMode m_mode = PresentationMode::VSync;
}; };

View File

@ -12,6 +12,8 @@
#include "drm_logging.h" #include "drm_logging.h"
#include "utils/realtime.h" #include "utils/realtime.h"
#include <span>
using namespace std::chrono_literals; using namespace std::chrono_literals;
namespace KWin namespace KWin
@ -116,7 +118,7 @@ void DrmCommitThread::submit()
auto toMerge = std::move(m_commits[1]); auto toMerge = std::move(m_commits[1]);
m_commits.erase(m_commits.begin() + 1); m_commits.erase(m_commits.begin() + 1);
m_commits.front()->merge(toMerge.get()); m_commits.front()->merge(toMerge.get());
m_droppedCommits.push_back(std::move(toMerge)); m_commitsToDelete.push_back(std::move(toMerge));
} }
if (commit->test()) { if (commit->test()) {
// presentation didn't fail after all, try again // presentation didn't fail after all, try again
@ -124,21 +126,24 @@ void DrmCommitThread::submit()
return; return;
} }
} }
const bool cursorOnly = std::ranges::all_of(m_commits, [](const auto &commit) {
return commit->isCursorOnly();
});
for (auto &commit : m_commits) { for (auto &commit : m_commits) {
m_droppedCommits.push_back(std::move(commit)); m_commitsToDelete.push_back(std::move(commit));
} }
m_commits.clear(); m_commits.clear();
qCWarning(KWIN_DRM) << "atomic commit failed:" << strerror(errno); qCWarning(KWIN_DRM) << "atomic commit failed:" << strerror(errno);
if (!cursorOnly) {
QMetaObject::invokeMethod(this, &DrmCommitThread::commitFailed, Qt::ConnectionType::QueuedConnection);
}
} }
QMetaObject::invokeMethod(this, &DrmCommitThread::clearDroppedCommits, Qt::ConnectionType::QueuedConnection); QMetaObject::invokeMethod(this, &DrmCommitThread::clearDroppedCommits, Qt::ConnectionType::QueuedConnection);
} }
static std::unique_ptr<DrmAtomicCommit> mergeCommits(std::span<const std::unique_ptr<DrmAtomicCommit>> commits)
{
auto ret = std::make_unique<DrmAtomicCommit>(*commits.front());
for (const auto &onTop : commits.subspan(1)) {
ret->merge(onTop.get());
}
return ret;
}
void DrmCommitThread::optimizeCommits() void DrmCommitThread::optimizeCommits()
{ {
if (m_commits.size() <= 1) { if (m_commits.size() <= 1) {
@ -146,22 +151,31 @@ void DrmCommitThread::optimizeCommits()
} }
// merge commits in the front that are already ready (regardless of which planes they modify) // merge commits in the front that are already ready (regardless of which planes they modify)
if (m_commits.front()->areBuffersReadable()) { if (m_commits.front()->areBuffersReadable()) {
auto it = m_commits.begin() + 1; const auto firstNotReadable = std::find_if(m_commits.begin() + 1, m_commits.end(), [](const auto &commit) {
while (it != m_commits.end() && (*it)->areBuffersReadable()) { return !commit->areBuffersReadable();
m_commits.front()->merge(it->get()); });
m_droppedCommits.push_back(std::move(*it)); if (firstNotReadable != m_commits.begin() + 1) {
it = m_commits.erase(it); auto merged = mergeCommits(std::span(m_commits.begin(), firstNotReadable));
std::move(m_commits.begin(), firstNotReadable, std::back_inserter(m_commitsToDelete));
m_commits.erase(m_commits.begin() + 1, firstNotReadable);
m_commits.front() = std::move(merged);
} }
} }
// merge commits that are ready and modify the same drm planes // merge commits that are ready and modify the same drm planes
for (auto it = m_commits.begin(); it != m_commits.end();) { for (auto it = m_commits.begin(); it != m_commits.end();) {
DrmAtomicCommit *const commit = it->get(); const auto startIt = it;
it++; auto &startCommit = *startIt;
while (it != m_commits.end() && commit->modifiedPlanes() == (*it)->modifiedPlanes() && (*it)->areBuffersReadable()) { const auto firstNotSamePlaneReadable = std::find_if(startIt + 1, m_commits.end(), [&startCommit](const auto &commit) {
commit->merge(it->get()); return startCommit->modifiedPlanes() != commit->modifiedPlanes() || !commit->areBuffersReadable();
m_droppedCommits.push_back(std::move(*it)); });
it = m_commits.erase(it); if (firstNotSamePlaneReadable == startIt + 1) {
it++;
continue;
} }
auto merged = mergeCommits(std::span(startIt, firstNotSamePlaneReadable));
std::move(startIt, firstNotSamePlaneReadable, std::back_inserter(m_commitsToDelete));
startCommit = std::move(merged);
it = m_commits.erase(startIt + 1, firstNotSamePlaneReadable);
} }
if (m_commits.size() == 1) { if (m_commits.size() == 1) {
// already done // already done
@ -169,7 +183,10 @@ void DrmCommitThread::optimizeCommits()
} }
std::unique_ptr<DrmAtomicCommit> front; std::unique_ptr<DrmAtomicCommit> front;
if (m_commits.front()->areBuffersReadable()) { if (m_commits.front()->areBuffersReadable()) {
front = std::move(m_commits.front()); // can't just move the commit, or merging might drop the last reference
// to an OutputFrame, which should only happen in the main thread
front = std::make_unique<DrmAtomicCommit>(*m_commits.front());
m_commitsToDelete.push_back(std::move(m_commits.front()));
m_commits.erase(m_commits.begin()); m_commits.erase(m_commits.begin());
} }
// try to move commits that are ready to the front // try to move commits that are ready to the front
@ -192,7 +209,7 @@ void DrmCommitThread::optimizeCommits()
duplicate = std::make_unique<DrmAtomicCommit>(*front); duplicate = std::make_unique<DrmAtomicCommit>(*front);
duplicate->merge(commit.get()); duplicate->merge(commit.get());
if (!duplicate->test()) { if (!duplicate->test()) {
m_droppedCommits.push_back(std::move(duplicate)); m_commitsToDelete.push_back(std::move(duplicate));
it++; it++;
continue; continue;
} }
@ -213,13 +230,14 @@ void DrmCommitThread::optimizeCommits()
} }
} }
} }
m_droppedCommits.push_back(std::move(duplicate)); m_commitsToDelete.push_back(std::move(duplicate));
if (success) { if (success) {
if (front) { if (front) {
front->merge(commit.get()); front->merge(commit.get());
m_droppedCommits.push_back(std::move(commit)); m_commitsToDelete.push_back(std::move(commit));
} else { } else {
front = std::move(commit); front = std::make_unique<DrmAtomicCommit>(*commit);
m_commitsToDelete.push_back(std::move(commit));
} }
it = m_commits.erase(it); it = m_commits.erase(it);
} else { } else {
@ -262,7 +280,7 @@ void DrmCommitThread::setPendingCommit(std::unique_ptr<DrmLegacyCommit> &&commit
void DrmCommitThread::clearDroppedCommits() void DrmCommitThread::clearDroppedCommits()
{ {
std::unique_lock lock(m_mutex); std::unique_lock lock(m_mutex);
m_droppedCommits.clear(); m_commitsToDelete.clear();
} }
void DrmCommitThread::setModeInfo(uint32_t maximum, std::chrono::nanoseconds vblankTime) void DrmCommitThread::setModeInfo(uint32_t maximum, std::chrono::nanoseconds vblankTime)

View File

@ -43,9 +43,6 @@ public:
*/ */
std::chrono::nanoseconds safetyMargin() const; std::chrono::nanoseconds safetyMargin() const;
Q_SIGNALS:
void commitFailed();
private: private:
void clearDroppedCommits(); void clearDroppedCommits();
TimePoint estimateNextVblank(TimePoint now) const; TimePoint estimateNextVblank(TimePoint now) const;
@ -60,7 +57,7 @@ private:
TimePoint m_lastPageflip; TimePoint m_lastPageflip;
TimePoint m_targetPageflipTime; TimePoint m_targetPageflipTime;
std::chrono::nanoseconds m_minVblankInterval; std::chrono::nanoseconds m_minVblankInterval;
std::vector<std::unique_ptr<DrmAtomicCommit>> m_droppedCommits; std::vector<std::unique_ptr<DrmAtomicCommit>> m_commitsToDelete;
bool m_vrr = false; bool m_vrr = false;
std::chrono::nanoseconds m_safetyMargin{0}; std::chrono::nanoseconds m_safetyMargin{0};
}; };

View File

@ -83,7 +83,7 @@ ColorDescription EglGbmLayer::colorDescription() const
return m_surface.colorDescription(); return m_surface.colorDescription();
} }
bool EglGbmLayer::doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color) bool EglGbmLayer::doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, const std::shared_ptr<OutputFrame> &frame)
{ {
static bool valid; static bool valid;
static const bool directScanoutDisabled = qEnvironmentVariableIntValue("KWIN_DRM_NO_DIRECT_SCANOUT", &valid) == 1 && valid; static const bool directScanoutDisabled = qEnvironmentVariableIntValue("KWIN_DRM_NO_DIRECT_SCANOUT", &valid) == 1 && valid;
@ -113,7 +113,7 @@ bool EglGbmLayer::doAttemptScanout(GraphicsBuffer *buffer, const ColorDescriptio
return false; return false;
} }
m_scanoutBuffer = m_pipeline->gpu()->importBuffer(buffer, FileDescriptor{}); m_scanoutBuffer = m_pipeline->gpu()->importBuffer(buffer, FileDescriptor{});
if (m_scanoutBuffer && m_pipeline->testScanout()) { if (m_scanoutBuffer && m_pipeline->testScanout(frame)) {
m_surface.forgetDamage(); // TODO: Use absolute frame sequence numbers for indexing the DamageJournal. It's more flexible and less error-prone m_surface.forgetDamage(); // TODO: Use absolute frame sequence numbers for indexing the DamageJournal. It's more flexible and less error-prone
return true; return true;
} else { } else {

View File

@ -39,7 +39,7 @@ public:
std::optional<QSize> fixedSize() const override; std::optional<QSize> fixedSize() const override;
private: private:
bool doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color) override; bool doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, const std::shared_ptr<OutputFrame> &frame) override;
std::shared_ptr<DrmFramebuffer> m_scanoutBuffer; std::shared_ptr<DrmFramebuffer> m_scanoutBuffer;

View File

@ -740,13 +740,16 @@ bool DrmGpu::isActive() const
bool DrmGpu::needsModeset() const bool DrmGpu::needsModeset() const
{ {
return m_forceModeset || std::ranges::any_of(m_pipelines, [](const auto &pipeline) { return std::any_of(m_pipelines.constBegin(), m_pipelines.constEnd(), [](const auto &pipeline) {
return pipeline->needsModeset(); return pipeline->needsModeset();
}); }) || m_forceModeset || !m_pendingModesetFrames.empty();
} }
bool DrmGpu::maybeModeset() bool DrmGpu::maybeModeset(const std::shared_ptr<OutputFrame> &frame)
{ {
if (frame) {
m_pendingModesetFrames.push_back(frame);
}
auto pipelines = m_pipelines; auto pipelines = m_pipelines;
for (const auto &output : std::as_const(m_drmOutputs)) { for (const auto &output : std::as_const(m_drmOutputs)) {
if (output->lease()) { if (output->lease()) {
@ -766,18 +769,20 @@ bool DrmGpu::maybeModeset()
for (DrmPipeline *pipeline : std::as_const(pipelines)) { for (DrmPipeline *pipeline : std::as_const(pipelines)) {
if (pipeline->modesetPresentPending()) { if (pipeline->modesetPresentPending()) {
pipeline->resetModesetPresentPending(); pipeline->resetModesetPresentPending();
if (err != DrmPipeline::Error::None) {
pipeline->output()->frameFailed();
}
} }
} }
m_forceModeset = false; m_forceModeset = false;
if (err == DrmPipeline::Error::None) { if (err == DrmPipeline::Error::None) {
for (const auto &frame : m_pendingModesetFrames) {
frame->presented(std::chrono::steady_clock::now().time_since_epoch(), PresentationMode::VSync);
}
m_pendingModesetFrames.clear();
return true; return true;
} else { } else {
if (err != DrmPipeline::Error::FramePending) { if (err != DrmPipeline::Error::FramePending) {
QTimer::singleShot(0, m_platform, &DrmBackend::updateOutputs); QTimer::singleShot(0, m_platform, &DrmBackend::updateOutputs);
} }
m_pendingModesetFrames.clear();
return false; return false;
} }
} }

View File

@ -36,6 +36,7 @@ class DrmVirtualOutput;
class EglDisplay; class EglDisplay;
class GraphicsBuffer; class GraphicsBuffer;
class GraphicsBufferAllocator; class GraphicsBufferAllocator;
class OutputFrame;
class DrmLease : public QObject class DrmLease : public QObject
{ {
@ -103,7 +104,7 @@ public:
DrmPipeline::Error testPendingConfiguration(); DrmPipeline::Error testPendingConfiguration();
bool needsModeset() const; bool needsModeset() const;
bool maybeModeset(); bool maybeModeset(const std::shared_ptr<OutputFrame> &frame);
std::shared_ptr<DrmFramebuffer> importBuffer(GraphicsBuffer *buffer, FileDescriptor &&explicitFence); std::shared_ptr<DrmFramebuffer> importBuffer(GraphicsBuffer *buffer, FileDescriptor &&explicitFence);
void releaseBuffers(); void releaseBuffers();
@ -159,6 +160,7 @@ private:
std::unique_ptr<QSocketNotifier> m_socketNotifier; std::unique_ptr<QSocketNotifier> m_socketNotifier;
QSize m_cursorSize; QSize m_cursorSize;
std::vector<std::shared_ptr<OutputFrame>> m_pendingModesetFrames;
}; };
} }

View File

@ -36,6 +36,7 @@ namespace KWin
{ {
static const bool s_allowColorspaceIntel = qEnvironmentVariableIntValue("KWIN_DRM_ALLOW_INTEL_COLORSPACE") == 1; static const bool s_allowColorspaceIntel = qEnvironmentVariableIntValue("KWIN_DRM_ALLOW_INTEL_COLORSPACE") == 1;
static const bool s_disableTripleBuffering = qEnvironmentVariableIntValue("KWIN_DRM_DISABLE_TRIPLE_BUFFERING") == 1;
DrmOutput::DrmOutput(const std::shared_ptr<DrmConnector> &conn) DrmOutput::DrmOutput(const std::shared_ptr<DrmConnector> &conn)
: DrmAbstractOutput(conn->gpu()) : DrmAbstractOutput(conn->gpu())
@ -44,6 +45,9 @@ DrmOutput::DrmOutput(const std::shared_ptr<DrmConnector> &conn)
{ {
m_pipeline->setOutput(this); m_pipeline->setOutput(this);
m_renderLoop->setRefreshRate(m_pipeline->mode()->refreshRate()); m_renderLoop->setRefreshRate(m_pipeline->mode()->refreshRate());
if (m_gpu->atomicModeSetting() && !s_disableTripleBuffering) {
m_renderLoop->setMaxPendingFrameCount(2);
}
Capabilities capabilities = Capability::Dpms | Capability::IccProfile; Capabilities capabilities = Capability::Dpms | Capability::IccProfile;
State initialState; State initialState;
@ -277,20 +281,19 @@ void DrmOutput::updateDpmsMode(DpmsMode dpmsMode)
bool DrmOutput::present(const std::shared_ptr<OutputFrame> &frame) bool DrmOutput::present(const std::shared_ptr<OutputFrame> &frame)
{ {
m_frame = frame;
const bool needsModeset = gpu()->needsModeset(); const bool needsModeset = gpu()->needsModeset();
bool success; bool success;
if (needsModeset) { if (needsModeset) {
m_pipeline->setPresentationMode(PresentationMode::VSync); m_pipeline->setPresentationMode(PresentationMode::VSync);
m_pipeline->setContentType(DrmConnector::DrmContentType::Graphics); m_pipeline->setContentType(DrmConnector::DrmContentType::Graphics);
success = m_pipeline->maybeModeset(); success = m_pipeline->maybeModeset(frame);
} else { } else {
m_pipeline->setPresentationMode(frame->presentationMode()); m_pipeline->setPresentationMode(frame->presentationMode());
DrmPipeline::Error err = m_pipeline->present(); DrmPipeline::Error err = m_pipeline->present(frame);
if (err != DrmPipeline::Error::None && frame->presentationMode() != PresentationMode::VSync) { if (err != DrmPipeline::Error::None && frame->presentationMode() != PresentationMode::VSync) {
// retry with a more basic presentation mode // retry with a more basic presentation mode
m_pipeline->setPresentationMode(PresentationMode::VSync); m_pipeline->setPresentationMode(PresentationMode::VSync);
err = m_pipeline->present(); err = m_pipeline->present(frame);
} }
success = err == DrmPipeline::Error::None; success = err == DrmPipeline::Error::None;
if (err == DrmPipeline::Error::InvalidArguments) { if (err == DrmPipeline::Error::InvalidArguments) {
@ -303,7 +306,6 @@ bool DrmOutput::present(const std::shared_ptr<OutputFrame> &frame)
return true; return true;
} else if (!needsModeset) { } else if (!needsModeset) {
qCWarning(KWIN_DRM) << "Presentation failed!" << strerror(errno); qCWarning(KWIN_DRM) << "Presentation failed!" << strerror(errno);
m_frame->failed();
} }
return false; return false;
} }
@ -406,7 +408,7 @@ void DrmOutput::applyQueuedChanges(const std::shared_ptr<OutputChangeSet> &props
setState(next); setState(next);
if (!isEnabled() && m_pipeline->needsModeset()) { if (!isEnabled() && m_pipeline->needsModeset()) {
m_gpu->maybeModeset(); m_gpu->maybeModeset(nullptr);
} }
m_renderLoop->setRefreshRate(refreshRate()); m_renderLoop->setRefreshRate(refreshRate());

View File

@ -42,11 +42,6 @@ DrmPipeline::DrmPipeline(DrmConnector *conn)
: m_connector(conn) : m_connector(conn)
, m_commitThread(std::make_unique<DrmCommitThread>(conn->gpu(), conn->connectorName())) , m_commitThread(std::make_unique<DrmCommitThread>(conn->gpu(), conn->connectorName()))
{ {
QObject::connect(m_commitThread.get(), &DrmCommitThread::commitFailed, [this]() {
if (m_output) {
m_output->frameFailed();
}
});
} }
DrmPipeline::~DrmPipeline() DrmPipeline::~DrmPipeline()
@ -56,13 +51,13 @@ DrmPipeline::~DrmPipeline()
} }
} }
bool DrmPipeline::testScanout() bool DrmPipeline::testScanout(const std::shared_ptr<OutputFrame> &frame)
{ {
if (gpu()->needsModeset()) { if (gpu()->needsModeset()) {
return false; return false;
} }
if (gpu()->atomicModeSetting()) { if (gpu()->atomicModeSetting()) {
return commitPipelines({this}, CommitMode::Test) == Error::None; return DrmPipeline::commitPipelinesAtomic({this}, CommitMode::Test, frame, {}) == Error::None;
} else { } else {
if (m_primaryLayer->currentBuffer()->buffer()->size() != m_pending.mode->size()) { if (m_primaryLayer->currentBuffer()->buffer()->size() != m_pending.mode->size()) {
// scaling isn't supported with the legacy API // scaling isn't supported with the legacy API
@ -70,7 +65,7 @@ bool DrmPipeline::testScanout()
} }
// no other way to test than to do it. // no other way to test than to do it.
// As we only have a maximum of one test per scanout cycle, this is fine // As we only have a maximum of one test per scanout cycle, this is fine
const bool ret = presentLegacy() == Error::None; const bool ret = presentLegacy(frame) == Error::None;
if (ret) { if (ret) {
m_didLegacyScanoutHack = true; m_didLegacyScanoutHack = true;
} }
@ -78,21 +73,17 @@ bool DrmPipeline::testScanout()
} }
} }
DrmPipeline::Error DrmPipeline::present() DrmPipeline::Error DrmPipeline::present(const std::shared_ptr<OutputFrame> &frame)
{ {
Q_ASSERT(m_pending.crtc); Q_ASSERT(m_pending.crtc);
if (gpu()->atomicModeSetting()) { if (gpu()->atomicModeSetting()) {
// test the full state, to take pending commits into account // test the full state, to take pending commits into account
auto fullState = std::make_unique<DrmAtomicCommit>(QList<DrmPipeline *>{this}); if (auto err = DrmPipeline::commitPipelinesAtomic({this}, CommitMode::Test, frame, {}); err != Error::None) {
if (Error err = prepareAtomicCommit(fullState.get(), CommitMode::Test); err != Error::None) {
return err; return err;
} }
if (!fullState->test()) {
return errnoToError();
}
// only give the actual state update to the commit thread, so that it can potentially reorder the commits // only give the actual state update to the commit thread, so that it can potentially reorder the commits
auto primaryPlaneUpdate = std::make_unique<DrmAtomicCommit>(QList<DrmPipeline *>{this}); auto primaryPlaneUpdate = std::make_unique<DrmAtomicCommit>(QList<DrmPipeline *>{this});
if (Error err = prepareAtomicPresentation(primaryPlaneUpdate.get()); err != Error::None) { if (Error err = prepareAtomicPresentation(primaryPlaneUpdate.get(), frame); err != Error::None) {
return err; return err;
} }
if (m_pending.needsModesetProperties && !prepareAtomicModeset(primaryPlaneUpdate.get())) { if (m_pending.needsModesetProperties && !prepareAtomicModeset(primaryPlaneUpdate.get())) {
@ -107,27 +98,27 @@ DrmPipeline::Error DrmPipeline::present()
m_didLegacyScanoutHack = false; m_didLegacyScanoutHack = false;
return Error::None; return Error::None;
} }
return presentLegacy(); return presentLegacy(frame);
} }
} }
bool DrmPipeline::maybeModeset() bool DrmPipeline::maybeModeset(const std::shared_ptr<OutputFrame> &frame)
{ {
m_modesetPresentPending = true; m_modesetPresentPending = true;
return gpu()->maybeModeset(); return gpu()->maybeModeset(frame);
} }
DrmPipeline::Error DrmPipeline::commitPipelines(const QList<DrmPipeline *> &pipelines, CommitMode mode, const QList<DrmObject *> &unusedObjects) DrmPipeline::Error DrmPipeline::commitPipelines(const QList<DrmPipeline *> &pipelines, CommitMode mode, const QList<DrmObject *> &unusedObjects)
{ {
Q_ASSERT(!pipelines.isEmpty()); Q_ASSERT(!pipelines.isEmpty());
if (pipelines[0]->gpu()->atomicModeSetting()) { if (pipelines[0]->gpu()->atomicModeSetting()) {
return commitPipelinesAtomic(pipelines, mode, unusedObjects); return commitPipelinesAtomic(pipelines, mode, nullptr, unusedObjects);
} else { } else {
return commitPipelinesLegacy(pipelines, mode, unusedObjects); return commitPipelinesLegacy(pipelines, mode, unusedObjects);
} }
} }
DrmPipeline::Error DrmPipeline::commitPipelinesAtomic(const QList<DrmPipeline *> &pipelines, CommitMode mode, const QList<DrmObject *> &unusedObjects) DrmPipeline::Error DrmPipeline::commitPipelinesAtomic(const QList<DrmPipeline *> &pipelines, CommitMode mode, const std::shared_ptr<OutputFrame> &frame, const QList<DrmObject *> &unusedObjects)
{ {
auto commit = std::make_unique<DrmAtomicCommit>(pipelines); auto commit = std::make_unique<DrmAtomicCommit>(pipelines);
if (mode == CommitMode::Test) { if (mode == CommitMode::Test) {
@ -141,7 +132,7 @@ DrmPipeline::Error DrmPipeline::commitPipelinesAtomic(const QList<DrmPipeline *>
} }
} }
for (const auto &pipeline : pipelines) { for (const auto &pipeline : pipelines) {
if (Error err = pipeline->prepareAtomicCommit(commit.get(), mode); err != Error::None) { if (Error err = pipeline->prepareAtomicCommit(commit.get(), mode, frame); err != Error::None) {
return err; return err;
} }
} }
@ -154,9 +145,9 @@ DrmPipeline::Error DrmPipeline::commitPipelinesAtomic(const QList<DrmPipeline *>
qCDebug(KWIN_DRM) << "Atomic modeset test failed!" << strerror(errno); qCDebug(KWIN_DRM) << "Atomic modeset test failed!" << strerror(errno);
return errnoToError(); return errnoToError();
} }
const bool withoutModeset = std::ranges::all_of(pipelines, [](DrmPipeline *pipeline) { const bool withoutModeset = std::ranges::all_of(pipelines, [&frame](DrmPipeline *pipeline) {
auto commit = std::make_unique<DrmAtomicCommit>(QVector<DrmPipeline *>{pipeline}); auto commit = std::make_unique<DrmAtomicCommit>(QVector<DrmPipeline *>{pipeline});
return pipeline->prepareAtomicCommit(commit.get(), CommitMode::TestAllowModeset) == Error::None && commit->test(); return pipeline->prepareAtomicCommit(commit.get(), CommitMode::TestAllowModeset, frame) == Error::None && commit->test();
}); });
for (const auto &pipeline : pipelines) { for (const auto &pipeline : pipelines) {
pipeline->m_pending.needsModeset = !withoutModeset; pipeline->m_pending.needsModeset = !withoutModeset;
@ -191,10 +182,10 @@ DrmPipeline::Error DrmPipeline::commitPipelinesAtomic(const QList<DrmPipeline *>
} }
} }
DrmPipeline::Error DrmPipeline::prepareAtomicCommit(DrmAtomicCommit *commit, CommitMode mode) DrmPipeline::Error DrmPipeline::prepareAtomicCommit(DrmAtomicCommit *commit, CommitMode mode, const std::shared_ptr<OutputFrame> &frame)
{ {
if (activePending()) { if (activePending()) {
if (Error err = prepareAtomicPresentation(commit); err != Error::None) { if (Error err = prepareAtomicPresentation(commit, frame); err != Error::None) {
return err; return err;
} }
if (m_pending.crtc->cursorPlane()) { if (m_pending.crtc->cursorPlane()) {
@ -211,7 +202,7 @@ DrmPipeline::Error DrmPipeline::prepareAtomicCommit(DrmAtomicCommit *commit, Com
return Error::None; return Error::None;
} }
DrmPipeline::Error DrmPipeline::prepareAtomicPresentation(DrmAtomicCommit *commit) DrmPipeline::Error DrmPipeline::prepareAtomicPresentation(DrmAtomicCommit *commit, const std::shared_ptr<OutputFrame> &frame)
{ {
commit->setPresentationMode(m_pending.presentationMode); commit->setPresentationMode(m_pending.presentationMode);
if (m_connector->contentType.isValid()) { if (m_connector->contentType.isValid()) {
@ -252,7 +243,7 @@ DrmPipeline::Error DrmPipeline::prepareAtomicPresentation(DrmAtomicCommit *commi
return Error::InvalidArguments; return Error::InvalidArguments;
} }
primary->set(commit, m_primaryLayer->sourceRect().toRect(), m_primaryLayer->targetRect()); primary->set(commit, m_primaryLayer->sourceRect().toRect(), m_primaryLayer->targetRect());
commit->addBuffer(m_pending.crtc->primaryPlane(), fb); commit->addBuffer(m_pending.crtc->primaryPlane(), fb, frame);
if (fb->buffer()->dmabufAttributes()->format == DRM_FORMAT_NV12) { if (fb->buffer()->dmabufAttributes()->format == DRM_FORMAT_NV12) {
if (!primary->colorEncoding.isValid() || !primary->colorRange.isValid()) { if (!primary->colorEncoding.isValid() || !primary->colorRange.isValid()) {
// don't allow NV12 direct scanout if we don't know what the driver will do // don't allow NV12 direct scanout if we don't know what the driver will do
@ -271,14 +262,14 @@ void DrmPipeline::prepareAtomicCursor(DrmAtomicCommit *commit)
if (layer->isEnabled()) { if (layer->isEnabled()) {
plane->set(commit, layer->sourceRect().toRect(), layer->targetRect()); plane->set(commit, layer->sourceRect().toRect(), layer->targetRect());
commit->addProperty(plane->crtcId, m_pending.crtc->id()); commit->addProperty(plane->crtcId, m_pending.crtc->id());
commit->addBuffer(plane, layer->currentBuffer()); commit->addBuffer(plane, layer->currentBuffer(), nullptr);
if (plane->vmHotspotX.isValid() && plane->vmHotspotY.isValid()) { if (plane->vmHotspotX.isValid() && plane->vmHotspotY.isValid()) {
commit->addProperty(plane->vmHotspotX, std::round(layer->hotspot().x())); commit->addProperty(plane->vmHotspotX, std::round(layer->hotspot().x()));
commit->addProperty(plane->vmHotspotY, std::round(layer->hotspot().y())); commit->addProperty(plane->vmHotspotY, std::round(layer->hotspot().y()));
} }
} else { } else {
commit->addProperty(plane->crtcId, 0); commit->addProperty(plane->crtcId, 0);
commit->addBuffer(plane, nullptr); commit->addBuffer(plane, nullptr, nullptr);
} }
} }
@ -412,12 +403,7 @@ bool DrmPipeline::updateCursor()
// explicitly check for the cursor plane and not for AMS, as we might not always have one // explicitly check for the cursor plane and not for AMS, as we might not always have one
if (m_pending.crtc->cursorPlane()) { if (m_pending.crtc->cursorPlane()) {
// test the full state, to take pending commits into account // test the full state, to take pending commits into account
auto fullState = std::make_unique<DrmAtomicCommit>(QList<DrmPipeline *>{this}); if (DrmPipeline::commitPipelinesAtomic({this}, CommitMode::Test, nullptr, {}) != Error::None) {
if (prepareAtomicPresentation(fullState.get()) != Error::None) {
return false;
}
prepareAtomicCursor(fullState.get());
if (!fullState->test()) {
return false; return false;
} }
// only give the actual state update to the commit thread, so that it can potentially reorder the commits // only give the actual state update to the commit thread, so that it can potentially reorder the commits
@ -456,19 +442,10 @@ DrmGpu *DrmPipeline::gpu() const
return m_connector->gpu(); return m_connector->gpu();
} }
void DrmPipeline::pageFlipped(std::chrono::nanoseconds timestamp, PageflipType type, PresentationMode mode) void DrmPipeline::pageFlipped(std::chrono::nanoseconds timestamp)
{ {
RenderLoopPrivate::get(m_output->renderLoop())->notifyVblank(timestamp);
m_commitThread->pageFlipped(timestamp); m_commitThread->pageFlipped(timestamp);
if (type == PageflipType::Modeset && !activePending()) {
return;
}
if (m_output) {
if (type == PageflipType::Normal || type == PageflipType::Modeset) {
m_output->pageFlipped(timestamp, mode);
} else {
RenderLoopPrivate::get(m_output->renderLoop())->notifyVblank(timestamp);
}
}
} }
void DrmPipeline::setOutput(DrmOutput *output) void DrmPipeline::setOutput(DrmOutput *output)

View File

@ -34,6 +34,7 @@ class GammaRamp;
class DrmConnectorMode; class DrmConnectorMode;
class DrmPipelineLayer; class DrmPipelineLayer;
class DrmCommitThread; class DrmCommitThread;
class OutputFrame;
class DrmGammaRamp class DrmGammaRamp
{ {
@ -69,9 +70,9 @@ public:
* tests the pending commit first and commits it if the test passes * tests the pending commit first and commits it if the test passes
* if the test fails, there is a guarantee for no lasting changes * if the test fails, there is a guarantee for no lasting changes
*/ */
Error present(); Error present(const std::shared_ptr<OutputFrame> &frame);
bool testScanout(); bool testScanout(const std::shared_ptr<OutputFrame> &frame);
bool maybeModeset(); bool maybeModeset(const std::shared_ptr<OutputFrame> &frame);
void forceLegacyModeset(); void forceLegacyModeset();
bool needsModeset() const; bool needsModeset() const;
@ -83,12 +84,7 @@ public:
DrmConnector *connector() const; DrmConnector *connector() const;
DrmGpu *gpu() const; DrmGpu *gpu() const;
enum class PageflipType { void pageFlipped(std::chrono::nanoseconds timestamp);
Normal,
CursorOnly,
Modeset,
};
void pageFlipped(std::chrono::nanoseconds timestamp, PageflipType type, PresentationMode mode);
bool pageflipsPending() const; bool pageflipsPending() const;
bool modesetPresentPending() const; bool modesetPresentPending() const;
void resetModesetPresentPending(); void resetModesetPresentPending();
@ -151,7 +147,7 @@ private:
std::shared_ptr<DrmBlob> createHdrMetadata(NamedTransferFunction transferFunction) const; std::shared_ptr<DrmBlob> createHdrMetadata(NamedTransferFunction transferFunction) const;
// legacy only // legacy only
Error presentLegacy(); Error presentLegacy(const std::shared_ptr<OutputFrame> &frame);
Error legacyModeset(); Error legacyModeset();
Error setLegacyGamma(); Error setLegacyGamma();
Error applyPendingChangesLegacy(); Error applyPendingChangesLegacy();
@ -159,12 +155,12 @@ private:
static Error commitPipelinesLegacy(const QList<DrmPipeline *> &pipelines, CommitMode mode, const QList<DrmObject *> &unusedObjects); static Error commitPipelinesLegacy(const QList<DrmPipeline *> &pipelines, CommitMode mode, const QList<DrmObject *> &unusedObjects);
// atomic modesetting only // atomic modesetting only
Error prepareAtomicCommit(DrmAtomicCommit *commit, CommitMode mode); Error prepareAtomicCommit(DrmAtomicCommit *commit, CommitMode mode, const std::shared_ptr<OutputFrame> &frame);
bool prepareAtomicModeset(DrmAtomicCommit *commit); bool prepareAtomicModeset(DrmAtomicCommit *commit);
Error prepareAtomicPresentation(DrmAtomicCommit *commit); Error prepareAtomicPresentation(DrmAtomicCommit *commit, const std::shared_ptr<OutputFrame> &frame);
void prepareAtomicCursor(DrmAtomicCommit *commit); void prepareAtomicCursor(DrmAtomicCommit *commit);
void prepareAtomicDisable(DrmAtomicCommit *commit); void prepareAtomicDisable(DrmAtomicCommit *commit);
static Error commitPipelinesAtomic(const QList<DrmPipeline *> &pipelines, CommitMode mode, const QList<DrmObject *> &unusedObjects); static Error commitPipelinesAtomic(const QList<DrmPipeline *> &pipelines, CommitMode mode, const std::shared_ptr<OutputFrame> &frame, const QList<DrmObject *> &unusedObjects);
DrmOutput *m_output = nullptr; DrmOutput *m_output = nullptr;
DrmConnector *m_connector = nullptr; DrmConnector *m_connector = nullptr;

View File

@ -24,7 +24,7 @@
namespace KWin namespace KWin
{ {
DrmPipeline::Error DrmPipeline::presentLegacy() DrmPipeline::Error DrmPipeline::presentLegacy(const std::shared_ptr<OutputFrame> &frame)
{ {
if (Error err = applyPendingChangesLegacy(); err != Error::None) { if (Error err = applyPendingChangesLegacy(); err != Error::None) {
return err; return err;
@ -33,7 +33,7 @@ DrmPipeline::Error DrmPipeline::presentLegacy()
if (m_primaryLayer->sourceRect() != m_primaryLayer->targetRect() || m_primaryLayer->targetRect() != QRect(QPoint(0, 0), buffer->buffer()->size())) { if (m_primaryLayer->sourceRect() != m_primaryLayer->targetRect() || m_primaryLayer->targetRect() != QRect(QPoint(0, 0), buffer->buffer()->size())) {
return Error::InvalidArguments; return Error::InvalidArguments;
} }
auto commit = std::make_unique<DrmLegacyCommit>(this, buffer); auto commit = std::make_unique<DrmLegacyCommit>(this, buffer, frame);
if (!commit->doPageflip(m_pending.presentationMode)) { if (!commit->doPageflip(m_pending.presentationMode)) {
qCWarning(KWIN_DRM) << "Page flip failed:" << strerror(errno); qCWarning(KWIN_DRM) << "Page flip failed:" << strerror(errno);
return errnoToError(); return errnoToError();
@ -57,7 +57,7 @@ DrmPipeline::Error DrmPipeline::legacyModeset()
if (m_primaryLayer->sourceRect() != QRect(QPoint(0, 0), buffer->buffer()->size())) { if (m_primaryLayer->sourceRect() != QRect(QPoint(0, 0), buffer->buffer()->size())) {
return Error::InvalidArguments; return Error::InvalidArguments;
} }
auto commit = std::make_unique<DrmLegacyCommit>(this, buffer); auto commit = std::make_unique<DrmLegacyCommit>(this, buffer, nullptr);
if (!commit->doModeset(m_connector, m_pending.mode.get())) { if (!commit->doModeset(m_connector, m_pending.mode.get())) {
qCWarning(KWIN_DRM) << "Modeset failed!" << strerror(errno); qCWarning(KWIN_DRM) << "Modeset failed!" << strerror(errno);
return errnoToError(); return errnoToError();
@ -84,7 +84,7 @@ DrmPipeline::Error DrmPipeline::commitPipelinesLegacy(const QList<DrmPipeline *>
for (const auto &pipeline : pipelines) { for (const auto &pipeline : pipelines) {
pipeline->applyPendingChanges(); pipeline->applyPendingChanges();
if (mode == CommitMode::CommitModeset && pipeline->activePending()) { if (mode == CommitMode::CommitModeset && pipeline->activePending()) {
pipeline->pageFlipped(std::chrono::steady_clock::now().time_since_epoch(), PageflipType::Normal, PresentationMode::VSync); pipeline->pageFlipped(std::chrono::steady_clock::now().time_since_epoch());
} }
} }
for (const auto &obj : unusedObjects) { for (const auto &obj : unusedObjects) {

View File

@ -162,7 +162,7 @@ void DrmPlane::setCurrentBuffer(const std::shared_ptr<DrmFramebuffer> &b)
void DrmPlane::disable(DrmAtomicCommit *commit) void DrmPlane::disable(DrmAtomicCommit *commit)
{ {
commit->addProperty(crtcId, 0); commit->addProperty(crtcId, 0);
commit->addBuffer(this, nullptr); commit->addBuffer(this, nullptr, nullptr);
} }
void DrmPlane::releaseCurrentBuffer() void DrmPlane::releaseCurrentBuffer()

View File

@ -133,7 +133,7 @@ std::shared_ptr<GLTexture> VirtualEglGbmLayer::texture() const
return nullptr; return nullptr;
} }
bool VirtualEglGbmLayer::doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color) bool VirtualEglGbmLayer::doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, const std::shared_ptr<OutputFrame> &frame)
{ {
static bool valid; static bool valid;
static const bool directScanoutDisabled = qEnvironmentVariableIntValue("KWIN_DRM_NO_DIRECT_SCANOUT", &valid) == 1 && valid; static const bool directScanoutDisabled = qEnvironmentVariableIntValue("KWIN_DRM_NO_DIRECT_SCANOUT", &valid) == 1 && valid;

View File

@ -44,7 +44,7 @@ public:
const ColorDescription &colorDescription() const; const ColorDescription &colorDescription() const;
private: private:
bool doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color) override; bool doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, const std::shared_ptr<OutputFrame> &frame) override;
std::shared_ptr<EglSwapchain> createGbmSwapchain() const; std::shared_ptr<EglSwapchain> createGbmSwapchain() const;
bool doesGbmSwapchainFit(EglSwapchain *swapchain) const; bool doesGbmSwapchainFit(EglSwapchain *swapchain) const;

View File

@ -50,15 +50,15 @@ bool DrmVirtualOutput::present(const std::shared_ptr<OutputFrame> &frame)
{ {
m_frame = frame; m_frame = frame;
m_vsyncMonitor->arm(); m_vsyncMonitor->arm();
m_pageFlipPending = true;
Q_EMIT outputChange(frame->damage()); Q_EMIT outputChange(frame->damage());
return true; return true;
} }
void DrmVirtualOutput::vblank(std::chrono::nanoseconds timestamp) void DrmVirtualOutput::vblank(std::chrono::nanoseconds timestamp)
{ {
if (m_pageFlipPending) { if (m_frame) {
DrmAbstractOutput::pageFlipped(timestamp, PresentationMode::VSync); m_frame->presented(timestamp, PresentationMode::VSync);
m_frame.reset();
} }
} }

View File

@ -39,7 +39,7 @@ private:
void setDpmsMode(DpmsMode mode) override; void setDpmsMode(DpmsMode mode) override;
std::shared_ptr<DrmOutputLayer> m_layer; std::shared_ptr<DrmOutputLayer> m_layer;
bool m_pageFlipPending = true; std::shared_ptr<OutputFrame> m_frame;
std::unique_ptr<SoftwareVsyncMonitor> m_vsyncMonitor; std::unique_ptr<SoftwareVsyncMonitor> m_vsyncMonitor;
}; };

View File

@ -75,8 +75,10 @@ void VirtualOutput::updateEnabled(bool enabled)
void VirtualOutput::vblank(std::chrono::nanoseconds timestamp) void VirtualOutput::vblank(std::chrono::nanoseconds timestamp)
{ {
m_frame->presented(std::chrono::nanoseconds(1'000'000'000'000 / refreshRate()), timestamp, PresentationMode::VSync); if (m_frame) {
m_frame.reset(); m_frame->presented(timestamp, PresentationMode::VSync);
m_frame.reset();
}
} }
} }

View File

@ -111,7 +111,7 @@ bool WaylandEglPrimaryLayer::doEndFrame(const QRegion &renderedRegion, const QRe
return true; return true;
} }
bool WaylandEglPrimaryLayer::doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color) bool WaylandEglPrimaryLayer::doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, const std::shared_ptr<OutputFrame> &frame)
{ {
Q_ASSERT(!m_presentationBuffer); Q_ASSERT(!m_presentationBuffer);
// TODO use viewporter to relax this check // TODO use viewporter to relax this check

View File

@ -44,7 +44,7 @@ public:
std::optional<OutputLayerBeginFrameInfo> doBeginFrame() override; std::optional<OutputLayerBeginFrameInfo> doBeginFrame() override;
bool doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame) override; bool doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame) override;
bool doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color) override; bool doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, const std::shared_ptr<OutputFrame> &frame) override;
DrmDevice *scanoutDevice() const override; DrmDevice *scanoutDevice() const override;
QHash<uint32_t, QList<uint64_t>> supportedDrmFormats() const override; QHash<uint32_t, QList<uint64_t>> supportedDrmFormats() const override;

View File

@ -125,8 +125,7 @@ WaylandOutput::WaylandOutput(const QString &name, WaylandBackend *backend)
connect(m_surface.get(), &KWayland::Client::Surface::frameRendered, this, [this]() { connect(m_surface.get(), &KWayland::Client::Surface::frameRendered, this, [this]() {
Q_ASSERT(m_frame); Q_ASSERT(m_frame);
const auto primary = Compositor::self()->backend()->primaryLayer(this); m_frame->presented(std::chrono::steady_clock::now().time_since_epoch(), PresentationMode::VSync);
m_frame->presented(std::chrono::nanoseconds(1'000'000'000'000 / refreshRate()), std::chrono::steady_clock::now().time_since_epoch(), PresentationMode::VSync);
m_frame.reset(); m_frame.reset();
}); });

View File

@ -441,7 +441,7 @@ OutputLayer *EglBackend::primaryLayer(Output *output)
void EglBackend::vblank(std::chrono::nanoseconds timestamp) void EglBackend::vblank(std::chrono::nanoseconds timestamp)
{ {
m_frame->presented(std::chrono::nanoseconds::zero(), timestamp, PresentationMode::VSync); m_frame->presented(timestamp, PresentationMode::VSync);
m_frame.reset(); m_frame.reset();
} }

View File

@ -714,7 +714,7 @@ void GlxBackend::present(Output *output, const std::shared_ptr<OutputFrame> &fra
void GlxBackend::vblank(std::chrono::nanoseconds timestamp) void GlxBackend::vblank(std::chrono::nanoseconds timestamp)
{ {
m_frame->presented(std::chrono::nanoseconds::zero(), timestamp, PresentationMode::VSync); m_frame->presented(timestamp, PresentationMode::VSync);
m_frame.reset(); m_frame.reset();
} }

View File

@ -308,7 +308,7 @@ void X11WindowedOutput::resize(const QSize &pixelSize)
void X11WindowedOutput::handlePresentCompleteNotify(xcb_present_complete_notify_event_t *event) void X11WindowedOutput::handlePresentCompleteNotify(xcb_present_complete_notify_event_t *event)
{ {
std::chrono::microseconds timestamp(event->ust); std::chrono::microseconds timestamp(event->ust);
m_frame->presented(std::chrono::nanoseconds(1'000'000'000'000 / refreshRate()), timestamp, PresentationMode::VSync); m_frame->presented(timestamp, PresentationMode::VSync);
m_frame.reset(); m_frame.reset();
} }

View File

@ -290,7 +290,7 @@ void WaylandCompositor::composite(RenderLoop *renderLoop)
superLayer->setOutputLayer(primaryLayer); superLayer->setOutputLayer(primaryLayer);
renderLoop->prepareNewFrame(); renderLoop->prepareNewFrame();
auto frame = std::make_shared<OutputFrame>(renderLoop); auto frame = std::make_shared<OutputFrame>(renderLoop, std::chrono::nanoseconds(1'000'000'000'000 / output->refreshRate()));
if (primaryLayer->needsRepaint() || superLayer->needsRepaint()) { if (primaryLayer->needsRepaint() || superLayer->needsRepaint()) {
renderLoop->beginPaint(); renderLoop->beginPaint();
@ -321,7 +321,7 @@ void WaylandCompositor::composite(RenderLoop *renderLoop)
}); });
if (scanoutPossible) { if (scanoutPossible) {
primaryLayer->setTargetRect(centerBuffer(output->transform().map(scanoutCandidate->size()), output->modeSize())); primaryLayer->setTargetRect(centerBuffer(output->transform().map(scanoutCandidate->size()), output->modeSize()));
directScanout = primaryLayer->attemptScanout(scanoutCandidate); directScanout = primaryLayer->attemptScanout(scanoutCandidate, frame);
} }
} else { } else {
primaryLayer->notifyNoScanoutCandidate(); primaryLayer->notifyNoScanoutCandidate();

View File

@ -457,7 +457,7 @@ void X11Compositor::composite(RenderLoop *renderLoop)
superLayer->setOutputLayer(primaryLayer); superLayer->setOutputLayer(primaryLayer);
renderLoop->prepareNewFrame(); renderLoop->prepareNewFrame();
auto frame = std::make_shared<OutputFrame>(renderLoop); auto frame = std::make_shared<OutputFrame>(renderLoop, std::chrono::nanoseconds(1'000'000'000'000) / renderLoop->refreshRate());
if (primaryLayer->needsRepaint() || superLayer->needsRepaint()) { if (primaryLayer->needsRepaint() || superLayer->needsRepaint()) {
renderLoop->beginPaint(); renderLoop->beginPaint();

View File

@ -62,12 +62,12 @@ bool OutputLayer::needsRepaint() const
return !m_repaints.isEmpty(); return !m_repaints.isEmpty();
} }
bool OutputLayer::doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color) bool OutputLayer::doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, const std::shared_ptr<OutputFrame> &frame)
{ {
return false; return false;
} }
bool OutputLayer::attemptScanout(SurfaceItem *surfaceItem) bool OutputLayer::attemptScanout(SurfaceItem *surfaceItem, const std::shared_ptr<OutputFrame> &frame)
{ {
SurfaceItemWayland *wayland = qobject_cast<SurfaceItemWayland *>(surfaceItem); SurfaceItemWayland *wayland = qobject_cast<SurfaceItemWayland *>(surfaceItem);
if (!wayland || !wayland->surface()) { if (!wayland || !wayland->surface()) {
@ -91,7 +91,7 @@ bool OutputLayer::attemptScanout(SurfaceItem *surfaceItem)
m_bufferTransform = surfaceItem->bufferTransform(); m_bufferTransform = surfaceItem->bufferTransform();
const auto desiredTransform = m_output ? m_output->transform() : OutputTransform::Kind::Normal; const auto desiredTransform = m_output ? m_output->transform() : OutputTransform::Kind::Normal;
m_offloadTransform = m_bufferTransform.combine(desiredTransform.inverted()); m_offloadTransform = m_bufferTransform.combine(desiredTransform.inverted());
const bool ret = doAttemptScanout(buffer, surfaceItem->colorDescription()); const bool ret = doAttemptScanout(buffer, surfaceItem->colorDescription(), frame);
if (ret) { if (ret) {
surfaceItem->resetDamage(); surfaceItem->resetDamage();
// ensure the pixmap is updated when direct scanout ends // ensure the pixmap is updated when direct scanout ends

View File

@ -65,7 +65,7 @@ public:
* Tries to import the newest buffer of the surface for direct scanout * Tries to import the newest buffer of the surface for direct scanout
* Returns @c true if scanout succeeds, @c false if rendering is necessary * Returns @c true if scanout succeeds, @c false if rendering is necessary
*/ */
bool attemptScanout(SurfaceItem *item); bool attemptScanout(SurfaceItem *item, const std::shared_ptr<OutputFrame> &frame);
/** /**
* Notify that there's no scanout candidate this frame * Notify that there's no scanout candidate this frame
@ -95,7 +95,7 @@ public:
OutputTransform bufferTransform() const; OutputTransform bufferTransform() const;
protected: protected:
virtual bool doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color); virtual bool doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, const std::shared_ptr<OutputFrame> &frame);
virtual std::optional<OutputLayerBeginFrameInfo> doBeginFrame() = 0; virtual std::optional<OutputLayerBeginFrameInfo> doBeginFrame() = 0;
virtual bool doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame) = 0; virtual bool doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame) = 0;

View File

@ -9,6 +9,8 @@
#include "scene/surfaceitem.h" #include "scene/surfaceitem.h"
#include "syncobjtimeline.h" #include "syncobjtimeline.h"
#include <QCoreApplication>
#include <QThread>
#include <drm_fourcc.h> #include <drm_fourcc.h>
#include <ranges> #include <ranges>
@ -42,12 +44,19 @@ std::optional<RenderTimeSpan> CpuRenderTimeQuery::query()
}; };
} }
OutputFrame::OutputFrame(RenderLoop *loop) OutputFrame::OutputFrame(RenderLoop *loop, std::chrono::nanoseconds refreshDuration)
: m_loop(loop) : m_loop(loop)
, m_refreshDuration(refreshDuration)
{ {
} }
OutputFrame::~OutputFrame() = default; OutputFrame::~OutputFrame()
{
Q_ASSERT(QThread::currentThread() == QCoreApplication::instance()->thread());
if (!m_presented) {
RenderLoopPrivate::get(m_loop)->notifyFrameDropped();
}
}
void OutputFrame::addFeedback(std::unique_ptr<PresentationFeedback> &&feedback) void OutputFrame::addFeedback(std::unique_ptr<PresentationFeedback> &&feedback)
{ {
@ -74,20 +83,16 @@ std::optional<std::chrono::nanoseconds> OutputFrame::queryRenderTime() const
return ret.end - ret.start; return ret.end - ret.start;
} }
void OutputFrame::presented(std::chrono::nanoseconds refreshDuration, std::chrono::nanoseconds timestamp, PresentationMode mode) void OutputFrame::presented(std::chrono::nanoseconds timestamp, PresentationMode mode)
{ {
std::optional<std::chrono::nanoseconds> renderTime = queryRenderTime(); std::optional<std::chrono::nanoseconds> renderTime = queryRenderTime();
RenderLoopPrivate::get(m_loop)->notifyFrameCompleted(timestamp, renderTime, mode); RenderLoopPrivate::get(m_loop)->notifyFrameCompleted(timestamp, renderTime, mode);
m_presented = true;
for (const auto &feedback : m_feedbacks) { for (const auto &feedback : m_feedbacks) {
feedback->presented(refreshDuration, timestamp, mode); feedback->presented(m_refreshDuration, timestamp, mode);
} }
} }
void OutputFrame::failed()
{
RenderLoopPrivate::get(m_loop)->notifyFrameFailed();
}
void OutputFrame::setContentType(ContentType type) void OutputFrame::setContentType(ContentType type)
{ {
m_contentType = type; m_contentType = type;

View File

@ -11,7 +11,7 @@
#include "utils/filedescriptor.h" #include "utils/filedescriptor.h"
#include <QObject> #include <QObject>
#include <QPointer>
#include <memory> #include <memory>
namespace KWin namespace KWin
@ -75,11 +75,10 @@ private:
class KWIN_EXPORT OutputFrame class KWIN_EXPORT OutputFrame
{ {
public: public:
explicit OutputFrame(RenderLoop *loop); explicit OutputFrame(RenderLoop *loop, std::chrono::nanoseconds refreshDuration);
~OutputFrame(); ~OutputFrame();
void presented(std::chrono::nanoseconds refreshDuration, std::chrono::nanoseconds timestamp, PresentationMode mode); void presented(std::chrono::nanoseconds timestamp, PresentationMode mode);
void failed();
void addFeedback(std::unique_ptr<PresentationFeedback> &&feedback); void addFeedback(std::unique_ptr<PresentationFeedback> &&feedback);
@ -97,11 +96,13 @@ private:
std::optional<std::chrono::nanoseconds> queryRenderTime() const; std::optional<std::chrono::nanoseconds> queryRenderTime() const;
RenderLoop *const m_loop; RenderLoop *const m_loop;
const std::chrono::nanoseconds m_refreshDuration;
std::vector<std::unique_ptr<PresentationFeedback>> m_feedbacks; std::vector<std::unique_ptr<PresentationFeedback>> m_feedbacks;
std::optional<ContentType> m_contentType; std::optional<ContentType> m_contentType;
PresentationMode m_presentationMode = PresentationMode::VSync; PresentationMode m_presentationMode = PresentationMode::VSync;
QRegion m_damage; QRegion m_damage;
std::vector<std::unique_ptr<RenderTimeQuery>> m_renderTimeQueries; std::vector<std::unique_ptr<RenderTimeQuery>> m_renderTimeQueries;
bool m_presented = false;
}; };
/** /**

View File

@ -12,15 +12,11 @@
#include "window.h" #include "window.h"
#include "workspace.h" #include "workspace.h"
using namespace std::chrono_literals;
namespace KWin namespace KWin
{ {
template<typename T>
T alignTimestamp(const T &timestamp, const T &alignment)
{
return timestamp + ((alignment - (timestamp % alignment)) % alignment);
}
RenderLoopPrivate *RenderLoopPrivate::get(RenderLoop *loop) RenderLoopPrivate *RenderLoopPrivate::get(RenderLoop *loop)
{ {
return loop->d.get(); return loop->d.get();
@ -36,36 +32,51 @@ RenderLoopPrivate::RenderLoopPrivate(RenderLoop *q, Output *output)
}); });
} }
void RenderLoopPrivate::scheduleRepaint() static std::chrono::nanoseconds estimateNextPageflip(std::chrono::nanoseconds earliestSubmitTime, std::chrono::nanoseconds lastPageflip, std::chrono::nanoseconds vblankInterval)
{
// the last pageflip may be in the future
const uint64_t pageflipsSince = earliestSubmitTime > lastPageflip ? (earliestSubmitTime - lastPageflip) / vblankInterval : 0;
return lastPageflip + vblankInterval * (pageflipsSince + 1);
}
void RenderLoopPrivate::scheduleNextRepaint()
{ {
if (kwinApp()->isTerminating() || compositeTimer.isActive()) { if (kwinApp()->isTerminating() || compositeTimer.isActive()) {
return; return;
} }
scheduleRepaint(nextPresentationTimestamp);
}
void RenderLoopPrivate::scheduleRepaint(std::chrono::nanoseconds lastTargetTimestamp)
{
pendingReschedule = false;
const std::chrono::nanoseconds vblankInterval(1'000'000'000'000ull / refreshRate); const std::chrono::nanoseconds vblankInterval(1'000'000'000'000ull / refreshRate);
const std::chrono::nanoseconds currentTime(std::chrono::steady_clock::now().time_since_epoch()); const std::chrono::nanoseconds currentTime(std::chrono::steady_clock::now().time_since_epoch());
// Estimate when the next presentation will occur. Note that this is a prediction.
nextPresentationTimestamp = lastPresentationTimestamp + vblankInterval;
if (nextPresentationTimestamp < currentTime && presentationMode == PresentationMode::VSync) {
nextPresentationTimestamp = lastPresentationTimestamp
+ alignTimestamp(currentTime - lastPresentationTimestamp, vblankInterval);
}
// Estimate when it's a good time to perform the next compositing cycle. // Estimate when it's a good time to perform the next compositing cycle.
// the 1ms on top of the safety margin is required for timer and scheduler inaccuracies // the 1ms on top of the safety margin is required for timer and scheduler inaccuracies
std::chrono::nanoseconds nextRenderTimestamp = nextPresentationTimestamp - renderJournal.result() - safetyMargin - std::chrono::milliseconds(1); const std::chrono::nanoseconds expectedCompositingTime = std::min(renderJournal.result() + safetyMargin + 1ms, 2 * vblankInterval);
// If we can't render the frame before the deadline, start compositing immediately. if (presentationMode == PresentationMode::VSync) {
if (nextRenderTimestamp < currentTime) { // normal presentation: pageflips only happen at vblank
nextRenderTimestamp = currentTime; if (maxPendingFrameCount == 1) {
} // keep the old behavior for backends not supporting triple buffering
nextPresentationTimestamp = estimateNextPageflip(currentTime, lastPresentationTimestamp, vblankInterval);
if (presentationMode == PresentationMode::Async || presentationMode == PresentationMode::AdaptiveAsync) { } else {
compositeTimer.start(0); // estimate the next pageflip that can realistically be hit
nextPresentationTimestamp = estimateNextPageflip(std::max(lastTargetTimestamp, currentTime + expectedCompositingTime), lastPresentationTimestamp, vblankInterval);
}
} else if (presentationMode == PresentationMode::Async || presentationMode == PresentationMode::AdaptiveAsync) {
// tearing: pageflips happen ASAP
nextPresentationTimestamp = currentTime;
} else { } else {
const std::chrono::nanoseconds waitInterval = nextRenderTimestamp - currentTime; // adaptive sync: pageflips happen after one vblank interval
compositeTimer.start(std::chrono::duration_cast<std::chrono::milliseconds>(waitInterval)); // TODO read minimum refresh rate from the EDID and take it into account here
nextPresentationTimestamp = lastPresentationTimestamp + vblankInterval;
} }
const std::chrono::nanoseconds nextRenderTimestamp = nextPresentationTimestamp - expectedCompositingTime;
compositeTimer.start(std::max(0ms, std::chrono::duration_cast<std::chrono::milliseconds>(nextRenderTimestamp - currentTime)));
} }
void RenderLoopPrivate::delayScheduleRepaint() void RenderLoopPrivate::delayScheduleRepaint()
@ -73,21 +84,13 @@ void RenderLoopPrivate::delayScheduleRepaint()
pendingReschedule = true; pendingReschedule = true;
} }
void RenderLoopPrivate::maybeScheduleRepaint() void RenderLoopPrivate::notifyFrameDropped()
{
if (pendingReschedule) {
scheduleRepaint();
pendingReschedule = false;
}
}
void RenderLoopPrivate::notifyFrameFailed()
{ {
Q_ASSERT(pendingFrameCount > 0); Q_ASSERT(pendingFrameCount > 0);
pendingFrameCount--; pendingFrameCount--;
if (!inhibitCount) { if (!inhibitCount && pendingReschedule) {
maybeScheduleRepaint(); scheduleNextRepaint();
} }
} }
@ -101,8 +104,12 @@ void RenderLoopPrivate::notifyFrameCompleted(std::chrono::nanoseconds timestamp,
if (renderTime) { if (renderTime) {
renderJournal.add(*renderTime, timestamp); renderJournal.add(*renderTime, timestamp);
} }
if (!inhibitCount) { if (compositeTimer.isActive()) {
maybeScheduleRepaint(); // reschedule to match the new timestamp and render time
scheduleRepaint(lastPresentationTimestamp);
}
if (!inhibitCount && pendingReschedule) {
scheduleNextRepaint();
} }
Q_EMIT q->framePresented(q, timestamp, mode); Q_EMIT q->framePresented(q, timestamp, mode);
@ -165,7 +172,7 @@ void RenderLoop::uninhibit()
d->inhibitCount--; d->inhibitCount--;
if (d->inhibitCount == 0) { if (d->inhibitCount == 0) {
d->maybeScheduleRepaint(); d->scheduleNextRepaint();
} }
} }
@ -210,8 +217,8 @@ void RenderLoop::scheduleRepaint(Item *item)
return; return;
} }
} }
if (!d->pendingFrameCount && !d->inhibitCount) { if (d->pendingFrameCount < d->maxPendingFrameCount && !d->inhibitCount) {
d->scheduleRepaint(); d->scheduleNextRepaint();
} else { } else {
d->delayScheduleRepaint(); d->delayScheduleRepaint();
} }
@ -232,6 +239,11 @@ void RenderLoop::setPresentationMode(PresentationMode mode)
d->presentationMode = mode; d->presentationMode = mode;
} }
void RenderLoop::setMaxPendingFrameCount(uint32_t maxCount)
{
d->maxPendingFrameCount = maxCount;
}
} // namespace KWin } // namespace KWin
#include "moc_renderloop.cpp" #include "moc_renderloop.cpp"

View File

@ -92,6 +92,8 @@ public:
void setPresentationMode(PresentationMode mode); void setPresentationMode(PresentationMode mode);
void setMaxPendingFrameCount(uint32_t maxCount);
Q_SIGNALS: Q_SIGNALS:
/** /**
* This signal is emitted when the refresh rate of this RenderLoop has changed. * This signal is emitted when the refresh rate of this RenderLoop has changed.

View File

@ -28,10 +28,10 @@ public:
void invalidate(); void invalidate();
void delayScheduleRepaint(); void delayScheduleRepaint();
void scheduleRepaint(); void scheduleNextRepaint();
void maybeScheduleRepaint(); void scheduleRepaint(std::chrono::nanoseconds lastTargetTimestamp);
void notifyFrameFailed(); void notifyFrameDropped();
void notifyFrameCompleted(std::chrono::nanoseconds timestamp, std::optional<std::chrono::nanoseconds> renderTime, PresentationMode mode = PresentationMode::VSync); void notifyFrameCompleted(std::chrono::nanoseconds timestamp, std::optional<std::chrono::nanoseconds> renderTime, PresentationMode mode = PresentationMode::VSync);
void notifyVblank(std::chrono::nanoseconds timestamp); void notifyVblank(std::chrono::nanoseconds timestamp);
@ -49,6 +49,7 @@ public:
std::chrono::nanoseconds safetyMargin{0}; std::chrono::nanoseconds safetyMargin{0};
PresentationMode presentationMode = PresentationMode::VSync; PresentationMode presentationMode = PresentationMode::VSync;
int maxPendingFrameCount = 1;
}; };
} // namespace KWin } // namespace KWin