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();
output->renderLoop()->prepareNewFrame();
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());
QVERIFY(output->present(frame));

View File

@ -27,17 +27,6 @@ RenderLoop *DrmAbstractOutput::renderLoop() const
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
{
return m_gpu;

View File

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

View File

@ -7,6 +7,7 @@
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "drm_commit.h"
#include "core/renderbackend.h"
#include "drm_blob.h"
#include "drm_buffer.h"
#include "drm_connector.h"
@ -54,10 +55,11 @@ void DrmAtomicCommit::addBlob(const DrmProperty &prop, const std::shared_ptr<Drm
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);
m_buffers[plane] = buffer;
m_frames[plane] = frame;
// atomic commits with IN_FENCE_FD fail with NVidia
if (plane->inFenceFd.isValid() && !plane->gpu()->isNVidia()) {
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;
}
void DrmAtomicCommit::pageFlipped(std::chrono::nanoseconds timestamp) const
void DrmAtomicCommit::pageFlipped(std::chrono::nanoseconds timestamp)
{
Q_ASSERT(QThread::currentThread() == QApplication::instance()->thread());
for (const auto &[plane, buffer] : m_buffers) {
plane->setCurrentBuffer(buffer);
}
DrmPipeline::PageflipType type = DrmPipeline::PageflipType::Normal;
if (m_modeset) {
type = DrmPipeline::PageflipType::Modeset;
} else if (m_cursorOnly) {
type = DrmPipeline::PageflipType::CursorOnly;
for (const auto &[plane, frame] : m_frames) {
if (frame) {
frame->presented(timestamp, m_mode);
}
}
m_frames.clear();
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) {
m_buffers[plane] = buffer;
m_frames[plane] = onTop->m_frames[plane];
m_planes.emplace(plane);
}
for (const auto &[prop, blob] : onTop->m_blobs) {
@ -207,16 +210,16 @@ bool DrmAtomicCommit::isCursorOnly() const
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())
, m_pipeline(pipeline)
, m_buffer(buffer)
, m_frame(frame)
{
}
bool DrmLegacyCommit::doModeset(DrmConnector *connector, DrmConnectorMode *mode)
{
m_modeset = true;
uint32_t connectorId = connector->id();
if (drmModeSetCrtc(gpu()->fd(), m_pipeline->crtc()->id(), m_buffer->framebufferId(), 0, 0, &connectorId, 1, mode->nativeMode()) == 0) {
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;
}
void DrmLegacyCommit::pageFlipped(std::chrono::nanoseconds timestamp) const
void DrmLegacyCommit::pageFlipped(std::chrono::nanoseconds timestamp)
{
Q_ASSERT(QThread::currentThread() == QApplication::instance()->thread());
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 DrmProperty;
class DrmPipeline;
class OutputFrame;
class DrmCommit
{
@ -39,7 +40,7 @@ public:
virtual ~DrmCommit();
DrmGpu *gpu() const;
virtual void pageFlipped(std::chrono::nanoseconds timestamp) const = 0;
virtual void pageFlipped(std::chrono::nanoseconds timestamp) = 0;
protected:
DrmCommit(DrmGpu *gpu);
@ -60,7 +61,7 @@ public:
addProperty(prop, prop.valueForEnum(enumValue));
}
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 setPresentationMode(PresentationMode mode);
@ -69,7 +70,7 @@ public:
bool commit();
bool commitModeset();
void pageFlipped(std::chrono::nanoseconds timestamp) const override;
void pageFlipped(std::chrono::nanoseconds timestamp) override;
bool areBuffersReadable() const;
void setDeadline(std::chrono::steady_clock::time_point deadline);
@ -87,6 +88,7 @@ private:
const QList<DrmPipeline *> m_pipelines;
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<OutputFrame>> m_frames;
std::unordered_set<DrmPlane *> m_planes;
std::optional<bool> m_vrr;
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
{
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 doPageflip(PresentationMode mode);
void pageFlipped(std::chrono::nanoseconds timestamp) const override;
void pageFlipped(std::chrono::nanoseconds timestamp) override;
private:
DrmPipeline *const m_pipeline;
const std::shared_ptr<DrmFramebuffer> m_buffer;
bool m_modeset = false;
std::shared_ptr<OutputFrame> m_frame;
PresentationMode m_mode = PresentationMode::VSync;
};

View File

@ -12,6 +12,8 @@
#include "drm_logging.h"
#include "utils/realtime.h"
#include <span>
using namespace std::chrono_literals;
namespace KWin
@ -116,7 +118,7 @@ void DrmCommitThread::submit()
auto toMerge = std::move(m_commits[1]);
m_commits.erase(m_commits.begin() + 1);
m_commits.front()->merge(toMerge.get());
m_droppedCommits.push_back(std::move(toMerge));
m_commitsToDelete.push_back(std::move(toMerge));
}
if (commit->test()) {
// presentation didn't fail after all, try again
@ -124,21 +126,24 @@ void DrmCommitThread::submit()
return;
}
}
const bool cursorOnly = std::ranges::all_of(m_commits, [](const auto &commit) {
return commit->isCursorOnly();
});
for (auto &commit : m_commits) {
m_droppedCommits.push_back(std::move(commit));
m_commitsToDelete.push_back(std::move(commit));
}
m_commits.clear();
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);
}
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()
{
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)
if (m_commits.front()->areBuffersReadable()) {
auto it = m_commits.begin() + 1;
while (it != m_commits.end() && (*it)->areBuffersReadable()) {
m_commits.front()->merge(it->get());
m_droppedCommits.push_back(std::move(*it));
it = m_commits.erase(it);
const auto firstNotReadable = std::find_if(m_commits.begin() + 1, m_commits.end(), [](const auto &commit) {
return !commit->areBuffersReadable();
});
if (firstNotReadable != m_commits.begin() + 1) {
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
for (auto it = m_commits.begin(); it != m_commits.end();) {
DrmAtomicCommit *const commit = it->get();
it++;
while (it != m_commits.end() && commit->modifiedPlanes() == (*it)->modifiedPlanes() && (*it)->areBuffersReadable()) {
commit->merge(it->get());
m_droppedCommits.push_back(std::move(*it));
it = m_commits.erase(it);
const auto startIt = it;
auto &startCommit = *startIt;
const auto firstNotSamePlaneReadable = std::find_if(startIt + 1, m_commits.end(), [&startCommit](const auto &commit) {
return startCommit->modifiedPlanes() != commit->modifiedPlanes() || !commit->areBuffersReadable();
});
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) {
// already done
@ -169,7 +183,10 @@ void DrmCommitThread::optimizeCommits()
}
std::unique_ptr<DrmAtomicCommit> front;
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());
}
// try to move commits that are ready to the front
@ -192,7 +209,7 @@ void DrmCommitThread::optimizeCommits()
duplicate = std::make_unique<DrmAtomicCommit>(*front);
duplicate->merge(commit.get());
if (!duplicate->test()) {
m_droppedCommits.push_back(std::move(duplicate));
m_commitsToDelete.push_back(std::move(duplicate));
it++;
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 (front) {
front->merge(commit.get());
m_droppedCommits.push_back(std::move(commit));
m_commitsToDelete.push_back(std::move(commit));
} else {
front = std::move(commit);
front = std::make_unique<DrmAtomicCommit>(*commit);
m_commitsToDelete.push_back(std::move(commit));
}
it = m_commits.erase(it);
} else {
@ -262,7 +280,7 @@ void DrmCommitThread::setPendingCommit(std::unique_ptr<DrmLegacyCommit> &&commit
void DrmCommitThread::clearDroppedCommits()
{
std::unique_lock lock(m_mutex);
m_droppedCommits.clear();
m_commitsToDelete.clear();
}
void DrmCommitThread::setModeInfo(uint32_t maximum, std::chrono::nanoseconds vblankTime)

View File

@ -43,9 +43,6 @@ public:
*/
std::chrono::nanoseconds safetyMargin() const;
Q_SIGNALS:
void commitFailed();
private:
void clearDroppedCommits();
TimePoint estimateNextVblank(TimePoint now) const;
@ -60,7 +57,7 @@ private:
TimePoint m_lastPageflip;
TimePoint m_targetPageflipTime;
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;
std::chrono::nanoseconds m_safetyMargin{0};
};

View File

@ -83,7 +83,7 @@ ColorDescription EglGbmLayer::colorDescription() const
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 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;
}
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
return true;
} else {

View File

@ -39,7 +39,7 @@ public:
std::optional<QSize> fixedSize() const override;
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;

View File

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

View File

@ -36,6 +36,7 @@ class DrmVirtualOutput;
class EglDisplay;
class GraphicsBuffer;
class GraphicsBufferAllocator;
class OutputFrame;
class DrmLease : public QObject
{
@ -103,7 +104,7 @@ public:
DrmPipeline::Error testPendingConfiguration();
bool needsModeset() const;
bool maybeModeset();
bool maybeModeset(const std::shared_ptr<OutputFrame> &frame);
std::shared_ptr<DrmFramebuffer> importBuffer(GraphicsBuffer *buffer, FileDescriptor &&explicitFence);
void releaseBuffers();
@ -159,6 +160,7 @@ private:
std::unique_ptr<QSocketNotifier> m_socketNotifier;
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_disableTripleBuffering = qEnvironmentVariableIntValue("KWIN_DRM_DISABLE_TRIPLE_BUFFERING") == 1;
DrmOutput::DrmOutput(const std::shared_ptr<DrmConnector> &conn)
: DrmAbstractOutput(conn->gpu())
@ -44,6 +45,9 @@ DrmOutput::DrmOutput(const std::shared_ptr<DrmConnector> &conn)
{
m_pipeline->setOutput(this);
m_renderLoop->setRefreshRate(m_pipeline->mode()->refreshRate());
if (m_gpu->atomicModeSetting() && !s_disableTripleBuffering) {
m_renderLoop->setMaxPendingFrameCount(2);
}
Capabilities capabilities = Capability::Dpms | Capability::IccProfile;
State initialState;
@ -277,20 +281,19 @@ void DrmOutput::updateDpmsMode(DpmsMode dpmsMode)
bool DrmOutput::present(const std::shared_ptr<OutputFrame> &frame)
{
m_frame = frame;
const bool needsModeset = gpu()->needsModeset();
bool success;
if (needsModeset) {
m_pipeline->setPresentationMode(PresentationMode::VSync);
m_pipeline->setContentType(DrmConnector::DrmContentType::Graphics);
success = m_pipeline->maybeModeset();
success = m_pipeline->maybeModeset(frame);
} else {
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) {
// retry with a more basic presentation mode
m_pipeline->setPresentationMode(PresentationMode::VSync);
err = m_pipeline->present();
err = m_pipeline->present(frame);
}
success = err == DrmPipeline::Error::None;
if (err == DrmPipeline::Error::InvalidArguments) {
@ -303,7 +306,6 @@ bool DrmOutput::present(const std::shared_ptr<OutputFrame> &frame)
return true;
} else if (!needsModeset) {
qCWarning(KWIN_DRM) << "Presentation failed!" << strerror(errno);
m_frame->failed();
}
return false;
}
@ -406,7 +408,7 @@ void DrmOutput::applyQueuedChanges(const std::shared_ptr<OutputChangeSet> &props
setState(next);
if (!isEnabled() && m_pipeline->needsModeset()) {
m_gpu->maybeModeset();
m_gpu->maybeModeset(nullptr);
}
m_renderLoop->setRefreshRate(refreshRate());

View File

@ -42,11 +42,6 @@ DrmPipeline::DrmPipeline(DrmConnector *conn)
: m_connector(conn)
, 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()
@ -56,13 +51,13 @@ DrmPipeline::~DrmPipeline()
}
}
bool DrmPipeline::testScanout()
bool DrmPipeline::testScanout(const std::shared_ptr<OutputFrame> &frame)
{
if (gpu()->needsModeset()) {
return false;
}
if (gpu()->atomicModeSetting()) {
return commitPipelines({this}, CommitMode::Test) == Error::None;
return DrmPipeline::commitPipelinesAtomic({this}, CommitMode::Test, frame, {}) == Error::None;
} else {
if (m_primaryLayer->currentBuffer()->buffer()->size() != m_pending.mode->size()) {
// scaling isn't supported with the legacy API
@ -70,7 +65,7 @@ bool DrmPipeline::testScanout()
}
// no other way to test than to do it.
// 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) {
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);
if (gpu()->atomicModeSetting()) {
// test the full state, to take pending commits into account
auto fullState = std::make_unique<DrmAtomicCommit>(QList<DrmPipeline *>{this});
if (Error err = prepareAtomicCommit(fullState.get(), CommitMode::Test); err != Error::None) {
if (auto err = DrmPipeline::commitPipelinesAtomic({this}, CommitMode::Test, frame, {}); err != Error::None) {
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
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;
}
if (m_pending.needsModesetProperties && !prepareAtomicModeset(primaryPlaneUpdate.get())) {
@ -107,27 +98,27 @@ DrmPipeline::Error DrmPipeline::present()
m_didLegacyScanoutHack = false;
return Error::None;
}
return presentLegacy();
return presentLegacy(frame);
}
}
bool DrmPipeline::maybeModeset()
bool DrmPipeline::maybeModeset(const std::shared_ptr<OutputFrame> &frame)
{
m_modesetPresentPending = true;
return gpu()->maybeModeset();
return gpu()->maybeModeset(frame);
}
DrmPipeline::Error DrmPipeline::commitPipelines(const QList<DrmPipeline *> &pipelines, CommitMode mode, const QList<DrmObject *> &unusedObjects)
{
Q_ASSERT(!pipelines.isEmpty());
if (pipelines[0]->gpu()->atomicModeSetting()) {
return commitPipelinesAtomic(pipelines, mode, unusedObjects);
return commitPipelinesAtomic(pipelines, mode, nullptr, unusedObjects);
} else {
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);
if (mode == CommitMode::Test) {
@ -141,7 +132,7 @@ DrmPipeline::Error DrmPipeline::commitPipelinesAtomic(const QList<DrmPipeline *>
}
}
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;
}
}
@ -154,9 +145,9 @@ DrmPipeline::Error DrmPipeline::commitPipelinesAtomic(const QList<DrmPipeline *>
qCDebug(KWIN_DRM) << "Atomic modeset test failed!" << strerror(errno);
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});
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) {
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 (Error err = prepareAtomicPresentation(commit); err != Error::None) {
if (Error err = prepareAtomicPresentation(commit, frame); err != Error::None) {
return err;
}
if (m_pending.crtc->cursorPlane()) {
@ -211,7 +202,7 @@ DrmPipeline::Error DrmPipeline::prepareAtomicCommit(DrmAtomicCommit *commit, Com
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);
if (m_connector->contentType.isValid()) {
@ -252,7 +243,7 @@ DrmPipeline::Error DrmPipeline::prepareAtomicPresentation(DrmAtomicCommit *commi
return Error::InvalidArguments;
}
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 (!primary->colorEncoding.isValid() || !primary->colorRange.isValid()) {
// 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()) {
plane->set(commit, layer->sourceRect().toRect(), layer->targetRect());
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()) {
commit->addProperty(plane->vmHotspotX, std::round(layer->hotspot().x()));
commit->addProperty(plane->vmHotspotY, std::round(layer->hotspot().y()));
}
} else {
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
if (m_pending.crtc->cursorPlane()) {
// test the full state, to take pending commits into account
auto fullState = std::make_unique<DrmAtomicCommit>(QList<DrmPipeline *>{this});
if (prepareAtomicPresentation(fullState.get()) != Error::None) {
return false;
}
prepareAtomicCursor(fullState.get());
if (!fullState->test()) {
if (DrmPipeline::commitPipelinesAtomic({this}, CommitMode::Test, nullptr, {}) != Error::None) {
return false;
}
// 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();
}
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);
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)

View File

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

View File

@ -24,7 +24,7 @@
namespace KWin
{
DrmPipeline::Error DrmPipeline::presentLegacy()
DrmPipeline::Error DrmPipeline::presentLegacy(const std::shared_ptr<OutputFrame> &frame)
{
if (Error err = applyPendingChangesLegacy(); err != Error::None) {
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())) {
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)) {
qCWarning(KWIN_DRM) << "Page flip failed:" << strerror(errno);
return errnoToError();
@ -57,7 +57,7 @@ DrmPipeline::Error DrmPipeline::legacyModeset()
if (m_primaryLayer->sourceRect() != QRect(QPoint(0, 0), buffer->buffer()->size())) {
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())) {
qCWarning(KWIN_DRM) << "Modeset failed!" << strerror(errno);
return errnoToError();
@ -84,7 +84,7 @@ DrmPipeline::Error DrmPipeline::commitPipelinesLegacy(const QList<DrmPipeline *>
for (const auto &pipeline : pipelines) {
pipeline->applyPendingChanges();
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) {

View File

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

View File

@ -133,7 +133,7 @@ std::shared_ptr<GLTexture> VirtualEglGbmLayer::texture() const
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 const bool directScanoutDisabled = qEnvironmentVariableIntValue("KWIN_DRM_NO_DIRECT_SCANOUT", &valid) == 1 && valid;

View File

@ -44,7 +44,7 @@ public:
const ColorDescription &colorDescription() const;
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;
bool doesGbmSwapchainFit(EglSwapchain *swapchain) const;

View File

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

View File

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

View File

@ -75,8 +75,10 @@ void VirtualOutput::updateEnabled(bool enabled)
void VirtualOutput::vblank(std::chrono::nanoseconds timestamp)
{
m_frame->presented(std::chrono::nanoseconds(1'000'000'000'000 / refreshRate()), timestamp, PresentationMode::VSync);
m_frame.reset();
if (m_frame) {
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;
}
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);
// TODO use viewporter to relax this check

View File

@ -44,7 +44,7 @@ public:
std::optional<OutputLayerBeginFrameInfo> doBeginFrame() 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;
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]() {
Q_ASSERT(m_frame);
const auto primary = Compositor::self()->backend()->primaryLayer(this);
m_frame->presented(std::chrono::nanoseconds(1'000'000'000'000 / refreshRate()), std::chrono::steady_clock::now().time_since_epoch(), PresentationMode::VSync);
m_frame->presented(std::chrono::steady_clock::now().time_since_epoch(), PresentationMode::VSync);
m_frame.reset();
});

View File

@ -441,7 +441,7 @@ OutputLayer *EglBackend::primaryLayer(Output *output)
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();
}

View File

@ -714,7 +714,7 @@ void GlxBackend::present(Output *output, const std::shared_ptr<OutputFrame> &fra
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();
}

View File

@ -308,7 +308,7 @@ void X11WindowedOutput::resize(const QSize &pixelSize)
void X11WindowedOutput::handlePresentCompleteNotify(xcb_present_complete_notify_event_t *event)
{
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();
}

View File

@ -290,7 +290,7 @@ void WaylandCompositor::composite(RenderLoop *renderLoop)
superLayer->setOutputLayer(primaryLayer);
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()) {
renderLoop->beginPaint();
@ -321,7 +321,7 @@ void WaylandCompositor::composite(RenderLoop *renderLoop)
});
if (scanoutPossible) {
primaryLayer->setTargetRect(centerBuffer(output->transform().map(scanoutCandidate->size()), output->modeSize()));
directScanout = primaryLayer->attemptScanout(scanoutCandidate);
directScanout = primaryLayer->attemptScanout(scanoutCandidate, frame);
}
} else {
primaryLayer->notifyNoScanoutCandidate();

View File

@ -457,7 +457,7 @@ void X11Compositor::composite(RenderLoop *renderLoop)
superLayer->setOutputLayer(primaryLayer);
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()) {
renderLoop->beginPaint();

View File

@ -62,12 +62,12 @@ bool OutputLayer::needsRepaint() const
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;
}
bool OutputLayer::attemptScanout(SurfaceItem *surfaceItem)
bool OutputLayer::attemptScanout(SurfaceItem *surfaceItem, const std::shared_ptr<OutputFrame> &frame)
{
SurfaceItemWayland *wayland = qobject_cast<SurfaceItemWayland *>(surfaceItem);
if (!wayland || !wayland->surface()) {
@ -91,7 +91,7 @@ bool OutputLayer::attemptScanout(SurfaceItem *surfaceItem)
m_bufferTransform = surfaceItem->bufferTransform();
const auto desiredTransform = m_output ? m_output->transform() : OutputTransform::Kind::Normal;
m_offloadTransform = m_bufferTransform.combine(desiredTransform.inverted());
const bool ret = doAttemptScanout(buffer, surfaceItem->colorDescription());
const bool ret = doAttemptScanout(buffer, surfaceItem->colorDescription(), frame);
if (ret) {
surfaceItem->resetDamage();
// 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
* 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
@ -95,7 +95,7 @@ public:
OutputTransform bufferTransform() const;
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 bool doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame) = 0;

View File

@ -9,6 +9,8 @@
#include "scene/surfaceitem.h"
#include "syncobjtimeline.h"
#include <QCoreApplication>
#include <QThread>
#include <drm_fourcc.h>
#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_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)
{
@ -74,20 +83,16 @@ std::optional<std::chrono::nanoseconds> OutputFrame::queryRenderTime() const
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();
RenderLoopPrivate::get(m_loop)->notifyFrameCompleted(timestamp, renderTime, mode);
m_presented = true;
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)
{
m_contentType = type;

View File

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

View File

@ -12,15 +12,11 @@
#include "window.h"
#include "workspace.h"
using namespace std::chrono_literals;
namespace KWin
{
template<typename T>
T alignTimestamp(const T &timestamp, const T &alignment)
{
return timestamp + ((alignment - (timestamp % alignment)) % alignment);
}
RenderLoopPrivate *RenderLoopPrivate::get(RenderLoop *loop)
{
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()) {
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 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.
// 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 (nextRenderTimestamp < currentTime) {
nextRenderTimestamp = currentTime;
}
if (presentationMode == PresentationMode::Async || presentationMode == PresentationMode::AdaptiveAsync) {
compositeTimer.start(0);
if (presentationMode == PresentationMode::VSync) {
// normal presentation: pageflips only happen at vblank
if (maxPendingFrameCount == 1) {
// keep the old behavior for backends not supporting triple buffering
nextPresentationTimestamp = estimateNextPageflip(currentTime, lastPresentationTimestamp, vblankInterval);
} else {
// 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 {
const std::chrono::nanoseconds waitInterval = nextRenderTimestamp - currentTime;
compositeTimer.start(std::chrono::duration_cast<std::chrono::milliseconds>(waitInterval));
// adaptive sync: pageflips happen after one vblank interval
// 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()
@ -73,21 +84,13 @@ void RenderLoopPrivate::delayScheduleRepaint()
pendingReschedule = true;
}
void RenderLoopPrivate::maybeScheduleRepaint()
{
if (pendingReschedule) {
scheduleRepaint();
pendingReschedule = false;
}
}
void RenderLoopPrivate::notifyFrameFailed()
void RenderLoopPrivate::notifyFrameDropped()
{
Q_ASSERT(pendingFrameCount > 0);
pendingFrameCount--;
if (!inhibitCount) {
maybeScheduleRepaint();
if (!inhibitCount && pendingReschedule) {
scheduleNextRepaint();
}
}
@ -101,8 +104,12 @@ void RenderLoopPrivate::notifyFrameCompleted(std::chrono::nanoseconds timestamp,
if (renderTime) {
renderJournal.add(*renderTime, timestamp);
}
if (!inhibitCount) {
maybeScheduleRepaint();
if (compositeTimer.isActive()) {
// reschedule to match the new timestamp and render time
scheduleRepaint(lastPresentationTimestamp);
}
if (!inhibitCount && pendingReschedule) {
scheduleNextRepaint();
}
Q_EMIT q->framePresented(q, timestamp, mode);
@ -165,7 +172,7 @@ void RenderLoop::uninhibit()
d->inhibitCount--;
if (d->inhibitCount == 0) {
d->maybeScheduleRepaint();
d->scheduleNextRepaint();
}
}
@ -210,8 +217,8 @@ void RenderLoop::scheduleRepaint(Item *item)
return;
}
}
if (!d->pendingFrameCount && !d->inhibitCount) {
d->scheduleRepaint();
if (d->pendingFrameCount < d->maxPendingFrameCount && !d->inhibitCount) {
d->scheduleNextRepaint();
} else {
d->delayScheduleRepaint();
}
@ -232,6 +239,11 @@ void RenderLoop::setPresentationMode(PresentationMode mode)
d->presentationMode = mode;
}
void RenderLoop::setMaxPendingFrameCount(uint32_t maxCount)
{
d->maxPendingFrameCount = maxCount;
}
} // namespace KWin
#include "moc_renderloop.cpp"

View File

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

View File

@ -28,10 +28,10 @@ public:
void invalidate();
void delayScheduleRepaint();
void scheduleRepaint();
void maybeScheduleRepaint();
void scheduleNextRepaint();
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 notifyVblank(std::chrono::nanoseconds timestamp);
@ -49,6 +49,7 @@ public:
std::chrono::nanoseconds safetyMargin{0};
PresentationMode presentationMode = PresentationMode::VSync;
int maxPendingFrameCount = 1;
};
} // namespace KWin