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 }