File indexing completed on 2024-11-10 04:56:27

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "drm_commit_thread.h"
0010 #include "drm_commit.h"
0011 #include "drm_gpu.h"
0012 #include "drm_logging.h"
0013 #include "utils/realtime.h"
0014 
0015 using namespace std::chrono_literals;
0016 
0017 namespace KWin
0018 {
0019 
0020 /**
0021  * This should always be longer than any real pageflip can take, even with PSR and modesets
0022  */
0023 static constexpr auto s_pageflipTimeout = 5s;
0024 
0025 DrmCommitThread::DrmCommitThread(DrmGpu *gpu, const QString &name)
0026 {
0027     if (!gpu->atomicModeSetting()) {
0028         return;
0029     }
0030     m_thread.reset(QThread::create([this]() {
0031         const auto thread = QThread::currentThread();
0032         gainRealTime();
0033         while (true) {
0034             if (thread->isInterruptionRequested()) {
0035                 return;
0036             }
0037             std::unique_lock lock(m_mutex);
0038             bool timeout = false;
0039             if (m_committed) {
0040                 timeout = m_commitPending.wait_for(lock, s_pageflipTimeout) == std::cv_status::timeout;
0041             } else if (m_commits.empty()) {
0042                 m_commitPending.wait(lock);
0043             }
0044             if (m_committed) {
0045                 // the commit would fail with EBUSY, wait until the pageflip is done
0046                 if (timeout) {
0047                     qCCritical(KWIN_DRM, "Pageflip timed out! This is a kernel bug");
0048                     std::unique_ptr<DrmAtomicCommit> committed(static_cast<DrmAtomicCommit *>(m_committed.release()));
0049                     const bool cursorOnly = committed->isCursorOnly();
0050                     m_droppedCommits.push_back(std::move(committed));
0051                     if (!cursorOnly) {
0052                         QMetaObject::invokeMethod(this, &DrmCommitThread::commitFailed, Qt::ConnectionType::QueuedConnection);
0053                     }
0054                 }
0055                 continue;
0056             }
0057             if (!m_commits.empty()) {
0058                 const auto now = std::chrono::steady_clock::now();
0059                 if (m_targetPageflipTime > now + m_safetyMargin) {
0060                     lock.unlock();
0061                     std::this_thread::sleep_until(m_targetPageflipTime - m_safetyMargin);
0062                     lock.lock();
0063                 }
0064                 optimizeCommits();
0065                 auto &commit = m_commits.front();
0066                 if (!commit->areBuffersReadable()) {
0067                     // no commit is ready yet, reschedule
0068                     if (m_vrr) {
0069                         m_targetPageflipTime += 50us;
0070                     } else {
0071                         m_targetPageflipTime += m_minVblankInterval;
0072                     }
0073                     continue;
0074                 }
0075                 const auto vrr = commit->isVrr();
0076                 const bool success = commit->commit();
0077                 if (success) {
0078                     m_vrr = vrr.value_or(m_vrr);
0079                     m_committed = std::move(commit);
0080                     m_commits.erase(m_commits.begin());
0081                 } else {
0082                     if (m_commits.size() > 1) {
0083                         // the failure may have been because of the reordering of commits
0084                         // -> collapse all commits into one and try again with an already tested state
0085                         while (m_commits.size() > 1) {
0086                             auto toMerge = std::move(m_commits[1]);
0087                             m_commits.erase(m_commits.begin() + 1);
0088                             m_commits.front()->merge(toMerge.get());
0089                             m_droppedCommits.push_back(std::move(toMerge));
0090                         }
0091                         if (commit->test()) {
0092                             // presentation didn't fail after all
0093                             continue;
0094                         }
0095                     }
0096                     const bool cursorOnly = std::all_of(m_commits.begin(), m_commits.end(), [](const auto &commit) {
0097                         return commit->isCursorOnly();
0098                     });
0099                     for (auto &commit : m_commits) {
0100                         m_droppedCommits.push_back(std::move(commit));
0101                     }
0102                     m_commits.clear();
0103                     qCWarning(KWIN_DRM) << "atomic commit failed:" << strerror(errno);
0104                     if (!cursorOnly) {
0105                         QMetaObject::invokeMethod(this, &DrmCommitThread::commitFailed, Qt::ConnectionType::QueuedConnection);
0106                     }
0107                 }
0108                 QMetaObject::invokeMethod(this, &DrmCommitThread::clearDroppedCommits, Qt::ConnectionType::QueuedConnection);
0109             }
0110         }
0111     }));
0112     m_thread->setObjectName(name);
0113     m_thread->start();
0114 }
0115 
0116 void DrmCommitThread::optimizeCommits()
0117 {
0118     if (m_commits.size() <= 1) {
0119         return;
0120     }
0121     // merge commits in the front that are already ready (regardless of which planes they modify)
0122     if (m_commits.front()->areBuffersReadable()) {
0123         auto it = m_commits.begin() + 1;
0124         while (it != m_commits.end() && (*it)->areBuffersReadable()) {
0125             m_commits.front()->merge(it->get());
0126             m_droppedCommits.push_back(std::move(*it));
0127             it = m_commits.erase(it);
0128         }
0129     }
0130     // merge commits that are ready and modify the same drm planes
0131     for (auto it = m_commits.begin(); it != m_commits.end();) {
0132         DrmAtomicCommit *const commit = it->get();
0133         it++;
0134         while (it != m_commits.end() && commit->modifiedPlanes() == (*it)->modifiedPlanes() && (*it)->areBuffersReadable()) {
0135             commit->merge(it->get());
0136             m_droppedCommits.push_back(std::move(*it));
0137             it = m_commits.erase(it);
0138         }
0139     }
0140     if (m_commits.size() == 1) {
0141         // already done
0142         return;
0143     }
0144     std::unique_ptr<DrmAtomicCommit> front;
0145     if (m_commits.front()->areBuffersReadable()) {
0146         front = std::move(m_commits.front());
0147         m_commits.erase(m_commits.begin());
0148     }
0149     // try to move commits that are ready to the front
0150     for (auto it = m_commits.begin() + 1; it != m_commits.end();) {
0151         auto &commit = *it;
0152         // commits that target the same plane(s) need to stay in the same order
0153         const auto &planes = commit->modifiedPlanes();
0154         const bool skipping = std::any_of(m_commits.begin(), it, [&planes](const auto &other) {
0155             return std::any_of(planes.begin(), planes.end(), [&other](DrmPlane *plane) {
0156                 return other->modifiedPlanes().contains(plane);
0157             });
0158         });
0159         if (skipping || !commit->areBuffersReadable()) {
0160             it++;
0161             continue;
0162         }
0163         // find out if the modified commit order will actually work
0164         std::unique_ptr<DrmAtomicCommit> duplicate;
0165         if (front) {
0166             duplicate = std::make_unique<DrmAtomicCommit>(*front);
0167             duplicate->merge(commit.get());
0168             if (!duplicate->test()) {
0169                 m_droppedCommits.push_back(std::move(duplicate));
0170                 it++;
0171                 continue;
0172             }
0173         } else {
0174             if (!commit->test()) {
0175                 it++;
0176                 continue;
0177             }
0178             duplicate = std::make_unique<DrmAtomicCommit>(*commit);
0179         }
0180         bool success = true;
0181         for (const auto &otherCommit : m_commits) {
0182             if (otherCommit != commit) {
0183                 duplicate->merge(otherCommit.get());
0184                 if (!duplicate->test()) {
0185                     success = false;
0186                     break;
0187                 }
0188             }
0189         }
0190         m_droppedCommits.push_back(std::move(duplicate));
0191         if (success) {
0192             if (front) {
0193                 front->merge(commit.get());
0194                 m_droppedCommits.push_back(std::move(commit));
0195             } else {
0196                 front = std::move(commit);
0197             }
0198             it = m_commits.erase(it);
0199         } else {
0200             it++;
0201         }
0202     }
0203     if (front) {
0204         m_commits.insert(m_commits.begin(), std::move(front));
0205     }
0206 }
0207 
0208 DrmCommitThread::~DrmCommitThread()
0209 {
0210     if (m_thread) {
0211         m_thread->requestInterruption();
0212         m_commitPending.notify_all();
0213         m_thread->wait();
0214     }
0215 }
0216 
0217 void DrmCommitThread::addCommit(std::unique_ptr<DrmAtomicCommit> &&commit)
0218 {
0219     std::unique_lock lock(m_mutex);
0220     m_commits.push_back(std::move(commit));
0221     const auto now = std::chrono::steady_clock::now();
0222     if (m_vrr && now >= m_lastPageflip + m_minVblankInterval) {
0223         m_targetPageflipTime = now;
0224     } else {
0225         m_targetPageflipTime = estimateNextVblank(now);
0226     }
0227     m_commits.back()->setDeadline(m_targetPageflipTime - m_safetyMargin);
0228     m_commitPending.notify_all();
0229 }
0230 
0231 void DrmCommitThread::setPendingCommit(std::unique_ptr<DrmLegacyCommit> &&commit)
0232 {
0233     m_committed = std::move(commit);
0234 }
0235 
0236 void DrmCommitThread::clearDroppedCommits()
0237 {
0238     std::unique_lock lock(m_mutex);
0239     m_droppedCommits.clear();
0240 }
0241 
0242 void DrmCommitThread::setModeInfo(uint32_t maximum, std::chrono::nanoseconds vblankTime)
0243 {
0244     std::unique_lock lock(m_mutex);
0245     m_minVblankInterval = std::chrono::nanoseconds(1'000'000'000'000ull / maximum);
0246     // the kernel rejects commits that happen during vblank
0247     // the 1.5ms on top of that was chosen experimentally, for the time it takes to commit + scheduling inaccuracies
0248     m_safetyMargin = vblankTime + 1500us;
0249 }
0250 
0251 void DrmCommitThread::pageFlipped(std::chrono::nanoseconds timestamp)
0252 {
0253     std::unique_lock lock(m_mutex);
0254     m_lastPageflip = TimePoint(timestamp);
0255     m_committed.reset();
0256     if (!m_commits.empty()) {
0257         m_targetPageflipTime = estimateNextVblank(std::chrono::steady_clock::now());
0258         m_commitPending.notify_all();
0259     }
0260 }
0261 
0262 bool DrmCommitThread::pageflipsPending()
0263 {
0264     std::unique_lock lock(m_mutex);
0265     return !m_commits.empty() || m_committed;
0266 }
0267 
0268 TimePoint DrmCommitThread::estimateNextVblank(TimePoint now) const
0269 {
0270     // the pageflip timestamp may be in the future
0271     const uint64_t pageflipsSince = now >= m_lastPageflip ? (now - m_lastPageflip) / m_minVblankInterval : 0;
0272     return m_lastPageflip + m_minVblankInterval * (pageflipsSince + 1);
0273 }
0274 
0275 std::chrono::nanoseconds DrmCommitThread::safetyMargin() const
0276 {
0277     return m_safetyMargin;
0278 }
0279 }