KWin
Loading...
Searching...
No Matches
drm_commit_thread.cpp
Go to the documentation of this file.
1/*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8*/
9#include "drm_commit_thread.h"
10#include "drm_commit.h"
11#include "drm_gpu.h"
12#include "drm_logging.h"
13#include "utils/realtime.h"
14
15using namespace std::chrono_literals;
16
17namespace KWin
18{
19
23static constexpr auto s_pageflipTimeout = 5s;
24
25DrmCommitThread::DrmCommitThread(DrmGpu *gpu, const QString &name)
26{
27 if (!gpu->atomicModeSetting()) {
28 return;
29 }
30 m_thread.reset(QThread::create([this]() {
31 const auto thread = QThread::currentThread();
33 while (true) {
34 if (thread->isInterruptionRequested()) {
35 return;
36 }
37 std::unique_lock lock(m_mutex);
38 bool timeout = false;
39 if (m_committed) {
40 timeout = m_commitPending.wait_for(lock, s_pageflipTimeout) == std::cv_status::timeout;
41 } else if (m_commits.empty()) {
42 m_commitPending.wait(lock);
43 }
44 if (m_committed) {
45 // the commit would fail with EBUSY, wait until the pageflip is done
46 if (timeout) {
47 qCCritical(KWIN_DRM, "Pageflip timed out! This is a kernel bug");
48 std::unique_ptr<DrmAtomicCommit> committed(static_cast<DrmAtomicCommit *>(m_committed.release()));
49 const bool cursorOnly = committed->isCursorOnly();
50 m_droppedCommits.push_back(std::move(committed));
51 if (!cursorOnly) {
52 QMetaObject::invokeMethod(this, &DrmCommitThread::commitFailed, Qt::ConnectionType::QueuedConnection);
53 }
54 }
55 continue;
56 }
57 if (!m_commits.empty()) {
58 const auto now = std::chrono::steady_clock::now();
59 if (m_targetPageflipTime > now + m_safetyMargin) {
60 lock.unlock();
61 std::this_thread::sleep_until(m_targetPageflipTime - m_safetyMargin);
62 lock.lock();
63 }
64 optimizeCommits();
65 auto &commit = m_commits.front();
66 if (!commit->areBuffersReadable()) {
67 // no commit is ready yet, reschedule
68 if (m_vrr) {
69 m_targetPageflipTime += 50us;
70 } else {
71 m_targetPageflipTime += m_minVblankInterval;
72 }
73 continue;
74 }
75 const auto vrr = commit->isVrr();
76 const bool success = commit->commit();
77 if (success) {
78 m_vrr = vrr.value_or(m_vrr);
79 m_committed = std::move(commit);
80 m_commits.erase(m_commits.begin());
81 } else {
82 if (m_commits.size() > 1) {
83 // the failure may have been because of the reordering of commits
84 // -> collapse all commits into one and try again with an already tested state
85 while (m_commits.size() > 1) {
86 auto toMerge = std::move(m_commits[1]);
87 m_commits.erase(m_commits.begin() + 1);
88 m_commits.front()->merge(toMerge.get());
89 m_droppedCommits.push_back(std::move(toMerge));
90 }
91 if (commit->test()) {
92 // presentation didn't fail after all
93 continue;
94 }
95 }
96 const bool cursorOnly = std::all_of(m_commits.begin(), m_commits.end(), [](const auto &commit) {
97 return commit->isCursorOnly();
98 });
99 for (auto &commit : m_commits) {
100 m_droppedCommits.push_back(std::move(commit));
101 }
102 m_commits.clear();
103 qCWarning(KWIN_DRM) << "atomic commit failed:" << strerror(errno);
104 if (!cursorOnly) {
105 QMetaObject::invokeMethod(this, &DrmCommitThread::commitFailed, Qt::ConnectionType::QueuedConnection);
106 }
107 }
108 QMetaObject::invokeMethod(this, &DrmCommitThread::clearDroppedCommits, Qt::ConnectionType::QueuedConnection);
109 }
110 }
111 }));
112 m_thread->setObjectName(name);
113 m_thread->start();
114}
115
116void DrmCommitThread::optimizeCommits()
117{
118 if (m_commits.size() <= 1) {
119 return;
120 }
121 // merge commits in the front that are already ready (regardless of which planes they modify)
122 if (m_commits.front()->areBuffersReadable()) {
123 auto it = m_commits.begin() + 1;
124 while (it != m_commits.end() && (*it)->areBuffersReadable()) {
125 m_commits.front()->merge(it->get());
126 m_droppedCommits.push_back(std::move(*it));
127 it = m_commits.erase(it);
128 }
129 }
130 // merge commits that are ready and modify the same drm planes
131 for (auto it = m_commits.begin(); it != m_commits.end();) {
132 DrmAtomicCommit *const commit = it->get();
133 it++;
134 while (it != m_commits.end() && commit->modifiedPlanes() == (*it)->modifiedPlanes() && (*it)->areBuffersReadable()) {
135 commit->merge(it->get());
136 m_droppedCommits.push_back(std::move(*it));
137 it = m_commits.erase(it);
138 }
139 }
140 if (m_commits.size() == 1) {
141 // already done
142 return;
143 }
144 std::unique_ptr<DrmAtomicCommit> front;
145 if (m_commits.front()->areBuffersReadable()) {
146 front = std::move(m_commits.front());
147 m_commits.erase(m_commits.begin());
148 }
149 // try to move commits that are ready to the front
150 for (auto it = m_commits.begin() + 1; it != m_commits.end();) {
151 auto &commit = *it;
152 // commits that target the same plane(s) need to stay in the same order
153 const auto &planes = commit->modifiedPlanes();
154 const bool skipping = std::any_of(m_commits.begin(), it, [&planes](const auto &other) {
155 return std::any_of(planes.begin(), planes.end(), [&other](DrmPlane *plane) {
156 return other->modifiedPlanes().contains(plane);
157 });
158 });
159 if (skipping || !commit->areBuffersReadable()) {
160 it++;
161 continue;
162 }
163 // find out if the modified commit order will actually work
164 std::unique_ptr<DrmAtomicCommit> duplicate;
165 if (front) {
166 duplicate = std::make_unique<DrmAtomicCommit>(*front);
167 duplicate->merge(commit.get());
168 if (!duplicate->test()) {
169 m_droppedCommits.push_back(std::move(duplicate));
170 it++;
171 continue;
172 }
173 } else {
174 if (!commit->test()) {
175 it++;
176 continue;
177 }
178 duplicate = std::make_unique<DrmAtomicCommit>(*commit);
179 }
180 bool success = true;
181 for (const auto &otherCommit : m_commits) {
182 if (otherCommit != commit) {
183 duplicate->merge(otherCommit.get());
184 if (!duplicate->test()) {
185 success = false;
186 break;
187 }
188 }
189 }
190 m_droppedCommits.push_back(std::move(duplicate));
191 if (success) {
192 if (front) {
193 front->merge(commit.get());
194 m_droppedCommits.push_back(std::move(commit));
195 } else {
196 front = std::move(commit);
197 }
198 it = m_commits.erase(it);
199 } else {
200 it++;
201 }
202 }
203 if (front) {
204 m_commits.insert(m_commits.begin(), std::move(front));
205 }
206}
207
208DrmCommitThread::~DrmCommitThread()
209{
210 if (m_thread) {
211 m_thread->requestInterruption();
212 m_commitPending.notify_all();
213 m_thread->wait();
214 }
215}
216
217void DrmCommitThread::addCommit(std::unique_ptr<DrmAtomicCommit> &&commit)
218{
219 std::unique_lock lock(m_mutex);
220 m_commits.push_back(std::move(commit));
221 const auto now = std::chrono::steady_clock::now();
222 if (m_vrr && now >= m_lastPageflip + m_minVblankInterval) {
223 m_targetPageflipTime = now;
224 } else {
225 m_targetPageflipTime = estimateNextVblank(now);
226 }
227 m_commits.back()->setDeadline(m_targetPageflipTime - m_safetyMargin);
228 m_commitPending.notify_all();
229}
230
231void DrmCommitThread::setPendingCommit(std::unique_ptr<DrmLegacyCommit> &&commit)
232{
233 m_committed = std::move(commit);
234}
235
236void DrmCommitThread::clearDroppedCommits()
237{
238 std::unique_lock lock(m_mutex);
239 m_droppedCommits.clear();
240}
241
242void DrmCommitThread::setModeInfo(uint32_t maximum, std::chrono::nanoseconds vblankTime)
243{
244 std::unique_lock lock(m_mutex);
245 m_minVblankInterval = std::chrono::nanoseconds(1'000'000'000'000ull / maximum);
246 // the kernel rejects commits that happen during vblank
247 // the 1.5ms on top of that was chosen experimentally, for the time it takes to commit + scheduling inaccuracies
248 m_safetyMargin = vblankTime + 1500us;
249}
250
251void DrmCommitThread::pageFlipped(std::chrono::nanoseconds timestamp)
252{
253 std::unique_lock lock(m_mutex);
254 m_lastPageflip = TimePoint(timestamp);
255 m_committed.reset();
256 if (!m_commits.empty()) {
257 m_targetPageflipTime = estimateNextVblank(std::chrono::steady_clock::now());
258 m_commitPending.notify_all();
259 }
260}
261
262bool DrmCommitThread::pageflipsPending()
263{
264 std::unique_lock lock(m_mutex);
265 return !m_commits.empty() || m_committed;
266}
267
268TimePoint DrmCommitThread::estimateNextVblank(TimePoint now) const
269{
270 // the pageflip timestamp may be in the future
271 const uint64_t pageflipsSince = now >= m_lastPageflip ? (now - m_lastPageflip) / m_minVblankInterval : 0;
272 return m_lastPageflip + m_minVblankInterval * (pageflipsSince + 1);
273}
274
275std::chrono::nanoseconds DrmCommitThread::safetyMargin() const
276{
277 return m_safetyMargin;
278}
279}
DrmCommitThread(DrmGpu *gpu, const QString &name)
bool atomicModeSetting() const
Definition drm_gpu.cpp:658
std::chrono::steady_clock::time_point TimePoint
void gainRealTime()
Definition realtime.cpp:16