KWin
Loading...
Searching...
No Matches
drm_gpu.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: 2020 Xaver Hugl <xaver.hugl@gmail.com>
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8*/
9#include "drm_gpu.h"
10
11#include <config-kwin.h>
12
14#include "core/session.h"
15#include "drm_backend.h"
16#include "drm_buffer.h"
17#include "drm_commit.h"
18#include "drm_connector.h"
19#include "drm_crtc.h"
20#include "drm_egl_backend.h"
21#include "drm_layer.h"
22#include "drm_logging.h"
23#include "drm_output.h"
24#include "drm_pipeline.h"
25#include "drm_plane.h"
26#include "drm_virtual_output.h"
27// system
28#include <algorithm>
29#include <errno.h>
30#include <fcntl.h>
31#include <poll.h>
32#include <unistd.h>
33// drm
34#include <drm_fourcc.h>
35#include <gbm.h>
36#include <libdrm/drm_mode.h>
37#include <xf86drm.h>
38#include <xf86drmMode.h>
39
40#ifndef DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT
41#define DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT 6
42#endif
43
44namespace KWin
45{
46
47DrmGpu::DrmGpu(DrmBackend *backend, const QString &devNode, int fd, dev_t deviceId)
48 : m_fd(fd)
49 , m_deviceId(deviceId)
50 , m_devNode(devNode)
51 , m_atomicModeSetting(false)
52 , m_gbmDevice(nullptr)
53 , m_platform(backend)
54{
55 uint64_t capability = 0;
56
57 if (drmGetCap(fd, DRM_CAP_CURSOR_WIDTH, &capability) == 0) {
58 m_cursorSize.setWidth(capability);
59 } else {
60 m_cursorSize.setWidth(64);
61 }
62
63 if (drmGetCap(fd, DRM_CAP_CURSOR_HEIGHT, &capability) == 0) {
64 m_cursorSize.setHeight(capability);
65 } else {
66 m_cursorSize.setHeight(64);
67 }
68
69 int ret = drmGetCap(fd, DRM_CAP_TIMESTAMP_MONOTONIC, &capability);
70 if (ret == 0 && capability == 1) {
71 m_presentationClock = CLOCK_MONOTONIC;
72 } else {
73 m_presentationClock = CLOCK_REALTIME;
74 }
75
76 m_addFB2ModifiersSupported = drmGetCap(fd, DRM_CAP_ADDFB2_MODIFIERS, &capability) == 0 && capability == 1;
77 qCDebug(KWIN_DRM) << "drmModeAddFB2WithModifiers is" << (m_addFB2ModifiersSupported ? "supported" : "not supported") << "on GPU" << m_devNode;
78
79 // find out what driver this kms device is using
81 m_isI915 = strstr(version->name, "i915");
82 m_isNVidia = strstr(version->name, "nvidia-drm");
83 m_isVirtualMachine = strstr(version->name, "virtio") || strstr(version->name, "qxl")
84 || strstr(version->name, "vmwgfx") || strstr(version->name, "vboxvideo");
85
86 // Reopen the drm node to create a new GEM handle namespace.
87 m_gbmFd = FileDescriptor{open(devNode.toLocal8Bit(), O_RDWR | O_CLOEXEC)};
88 if (m_gbmFd.isValid()) {
89 drm_magic_t magic;
90 drmGetMagic(m_gbmFd.get(), &magic);
91 drmAuthMagic(m_fd, magic);
92 m_gbmDevice = gbm_create_device(m_gbmFd.get());
93 if (!m_gbmDevice) {
94 qCCritical(KWIN_DRM) << "gbm_create_device() failed";
95 } else {
96 m_allocator = std::make_unique<GbmGraphicsBufferAllocator>(m_gbmDevice);
97 }
98 }
99
100 m_socketNotifier = std::make_unique<QSocketNotifier>(fd, QSocketNotifier::Read);
101 connect(m_socketNotifier.get(), &QSocketNotifier::activated, this, &DrmGpu::dispatchEvents);
102
103 initDrmResources();
104
105 if (m_atomicModeSetting == false) {
106 // only supported with legacy
107 m_asyncPageflipSupported = drmGetCap(fd, DRM_CAP_ASYNC_PAGE_FLIP, &capability) == 0 && capability == 1;
108 }
109}
110
112{
114 m_eglDisplay.reset();
115 m_crtcs.clear();
116 m_connectors.clear();
117 m_planes.clear();
118 m_socketNotifier.reset();
119 m_allocator.reset();
120 if (m_gbmDevice) {
121 gbm_device_destroy(m_gbmDevice);
122 }
123 m_gbmFd = FileDescriptor{};
124 m_platform->session()->closeRestricted(m_fd);
125}
126
128{
129 char *path = drmGetDeviceNameFromFd2(m_fd);
130 FileDescriptor fd{open(path, O_RDWR | O_CLOEXEC)};
131 if (!fd.isValid()) {
132 qCWarning(KWIN_DRM) << "Could not open DRM fd for leasing!" << strerror(errno);
133 } else {
134 if (drmIsMaster(fd.get())) {
135 if (drmDropMaster(fd.get()) != 0) {
136 qCWarning(KWIN_DRM) << "Could not create a non-master DRM fd for leasing!" << strerror(errno);
137 return FileDescriptor{};
138 }
139 }
140 }
141 return fd;
142}
143
145{
146 return m_presentationClock;
147}
148
149void DrmGpu::initDrmResources()
150{
151 // try atomic mode setting
152 bool isEnvVarSet = false;
153 const bool supportsVmCursorHotspot = drmSetClientCap(m_fd, DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT, 1) == 0;
154 const bool noAMS = qEnvironmentVariableIntValue("KWIN_DRM_NO_AMS", &isEnvVarSet) != 0 && isEnvVarSet;
155 if (m_isVirtualMachine && !supportsVmCursorHotspot && !isEnvVarSet) {
156 qCWarning(KWIN_DRM, "Atomic Mode Setting disabled on GPU %s because of cursor offset issues in virtual machines", qPrintable(m_devNode));
157 } else if (noAMS) {
158 qCWarning(KWIN_DRM) << "Atomic Mode Setting requested off via environment variable. Using legacy mode on GPU" << m_devNode;
159 } else if (drmSetClientCap(m_fd, DRM_CLIENT_CAP_ATOMIC, 1) != 0) {
160 qCWarning(KWIN_DRM) << "drmSetClientCap for Atomic Mode Setting failed. Using legacy mode on GPU" << m_devNode;
161 } else {
162 DrmUniquePtr<drmModePlaneRes> planeResources(drmModeGetPlaneResources(m_fd));
163 if (planeResources) {
164 qCDebug(KWIN_DRM) << "Using Atomic Mode Setting on gpu" << m_devNode;
165 qCDebug(KWIN_DRM) << "Number of planes on GPU" << m_devNode << ":" << planeResources->count_planes;
166 // create the plane objects
167 for (unsigned int i = 0; i < planeResources->count_planes; ++i) {
168 DrmUniquePtr<drmModePlane> kplane(drmModeGetPlane(m_fd, planeResources->planes[i]));
169 auto plane = std::make_unique<DrmPlane>(this, kplane->plane_id);
170 if (plane->init()) {
171 m_allObjects << plane.get();
172 m_planes.push_back(std::move(plane));
173 }
174 }
175 if (m_planes.empty()) {
176 qCWarning(KWIN_DRM) << "Failed to create any plane. Falling back to legacy mode on GPU " << m_devNode;
177 }
178 } else {
179 qCWarning(KWIN_DRM) << "Failed to get plane resources. Falling back to legacy mode on GPU " << m_devNode;
180 }
181 }
182 m_atomicModeSetting = !m_planes.empty();
183
184 DrmUniquePtr<drmModeRes> resources(drmModeGetResources(m_fd));
185 if (!resources) {
186 qCCritical(KWIN_DRM) << "drmModeGetResources for getting CRTCs failed on GPU" << m_devNode;
187 return;
188 }
189 QList<DrmPlane *> assignedPlanes;
190 for (int i = 0; i < resources->count_crtcs; ++i) {
191 uint32_t crtcId = resources->crtcs[i];
192 QList<DrmPlane *> primaryCandidates;
193 QList<DrmPlane *> cursorCandidates;
194 for (const auto &plane : m_planes) {
195 if (plane->isCrtcSupported(i) && !assignedPlanes.contains(plane.get())) {
196 if (plane->type.enumValue() == DrmPlane::TypeIndex::Primary) {
197 primaryCandidates.push_back(plane.get());
198 } else if (plane->type.enumValue() == DrmPlane::TypeIndex::Cursor) {
199 cursorCandidates.push_back(plane.get());
200 }
201 }
202 }
203 if (m_atomicModeSetting && primaryCandidates.empty()) {
204 qCWarning(KWIN_DRM) << "Could not find a suitable primary plane for crtc" << resources->crtcs[i];
205 continue;
206 }
207 const auto findBestPlane = [crtcId](const QList<DrmPlane *> &list) {
208 // if the plane is already used with this crtc, prefer it
209 const auto connected = std::find_if(list.begin(), list.end(), [crtcId](DrmPlane *plane) {
210 return plane->crtcId.value() == crtcId;
211 });
212 if (connected != list.end()) {
213 return *connected;
214 }
215 // don't take away planes from other crtcs. The kernel currently rejects such commits
216 const auto notconnected = std::find_if(list.begin(), list.end(), [](DrmPlane *plane) {
217 return plane->crtcId.value() == 0;
218 });
219 if (notconnected != list.end()) {
220 return *notconnected;
221 }
222 return list.empty() ? nullptr : list.front();
223 };
224 DrmPlane *primary = findBestPlane(primaryCandidates);
225 DrmPlane *cursor = findBestPlane(cursorCandidates);
226 assignedPlanes.push_back(primary);
227 if (cursor) {
228 assignedPlanes.push_back(cursor);
229 }
230 auto crtc = std::make_unique<DrmCrtc>(this, crtcId, i, primary, cursor);
231 if (!crtc->init()) {
232 continue;
233 }
234 m_allObjects << crtc.get();
235 m_crtcs.push_back(std::move(crtc));
236 }
237}
238
240{
241 if (!m_isActive) {
242 return false;
243 }
244 waitIdle();
246 if (!resources) {
247 qCWarning(KWIN_DRM) << "drmModeGetResources failed";
248 return false;
249 }
250
251 // In principle these things are supposed to be detected through the wayland protocol.
252 // In practice SteamVR doesn't always behave correctly
254 for (const auto &output : std::as_const(m_drmOutputs)) {
255 if (output->lease()) {
256 bool leaseActive = false;
257 for (uint i = 0; i < lessees->count; i++) {
258 if (lessees->lessees[i] == output->lease()->lesseeId()) {
259 leaseActive = true;
260 break;
261 }
262 }
263 if (!leaseActive) {
264 Q_EMIT output->lease()->revokeRequested();
265 }
266 }
267 }
268
269 // check for added and removed connectors
270 QList<DrmConnector *> existing;
271 QList<DrmOutput *> addedOutputs;
272 for (int i = 0; i < resources->count_connectors; ++i) {
273 const uint32_t currentConnector = resources->connectors[i];
274 const auto it = std::find_if(m_connectors.begin(), m_connectors.end(), [currentConnector](const auto &connector) {
275 return connector->id() == currentConnector;
276 });
277 if (it == m_connectors.end()) {
278 auto conn = std::make_shared<DrmConnector>(this, currentConnector);
279 if (!conn->init()) {
280 continue;
281 }
282 existing.push_back(conn.get());
283 m_allObjects.push_back(conn.get());
284 m_connectors.push_back(std::move(conn));
285 } else {
286 (*it)->updateProperties();
287 existing.push_back(it->get());
288 }
289 }
290 for (auto it = m_connectors.begin(); it != m_connectors.end();) {
291 DrmConnector *conn = it->get();
292 const auto output = findOutput(conn->id());
293 const bool stillExists = existing.contains(conn);
294 if (!stillExists || !conn->isConnected()) {
295 if (output) {
296 removeOutput(output);
297 }
298 } else if (!output) {
299 qCDebug(KWIN_DRM, "New %soutput on GPU %s: %s", conn->isNonDesktop() ? "non-desktop " : "", qPrintable(m_devNode), qPrintable(conn->modelName()));
300 const auto pipeline = conn->pipeline();
301 m_pipelines << pipeline;
302 auto output = new DrmOutput(*it);
303 m_drmOutputs << output;
304 addedOutputs << output;
305 Q_EMIT outputAdded(output);
306 pipeline->setLayers(m_platform->renderBackend()->createPrimaryLayer(pipeline), m_platform->renderBackend()->createCursorLayer(pipeline));
307 pipeline->setActive(!conn->isNonDesktop());
308 pipeline->applyPendingChanges();
309 }
310 if (stillExists) {
311 it++;
312 } else {
313 m_allObjects.removeOne(it->get());
314 it = m_connectors.erase(it);
315 }
316 }
317
318 // update crtc properties
319 for (const auto &crtc : std::as_const(m_crtcs)) {
320 crtc->updateProperties();
321 }
322 // update plane properties
323 for (const auto &plane : std::as_const(m_planes)) {
324 plane->updateProperties();
325 }
327 if (err == DrmPipeline::Error::None) {
328 for (const auto &pipeline : std::as_const(m_pipelines)) {
329 pipeline->applyPendingChanges();
330 if (pipeline->output() && !pipeline->crtc()) {
331 pipeline->setEnable(false);
332 pipeline->output()->updateEnabled(false);
333 }
334 }
335 } else if (err == DrmPipeline::Error::NoPermission) {
336 for (const auto &pipeline : std::as_const(m_pipelines)) {
337 pipeline->revertPendingChanges();
338 }
339 for (const auto &output : std::as_const(addedOutputs)) {
340 removeOutput(output);
341 const auto it = std::find_if(m_connectors.begin(), m_connectors.end(), [output](const auto &conn) {
342 return conn.get() == output->connector();
343 });
344 Q_ASSERT(it != m_connectors.end());
345 m_allObjects.removeOne(it->get());
346 m_connectors.erase(it);
347 }
348 } else {
349 qCWarning(KWIN_DRM, "Failed to find a working setup for new outputs!");
350 for (const auto &pipeline : std::as_const(m_pipelines)) {
351 pipeline->revertPendingChanges();
352 }
353 for (const auto &output : std::as_const(addedOutputs)) {
354 output->updateEnabled(false);
355 output->pipeline()->setEnable(false);
356 output->pipeline()->applyPendingChanges();
357 }
358 }
359 return true;
360}
361
363{
364 const auto outputs = m_drmOutputs;
365 for (const auto &output : outputs) {
366 removeOutput(output);
367 }
368 const auto virtualOutputs = m_virtualOutputs;
369 for (const auto &output : virtualOutputs) {
370 removeVirtualOutput(output);
371 }
372}
373
374DrmPipeline::Error DrmGpu::checkCrtcAssignment(QList<DrmConnector *> connectors, const QList<DrmCrtc *> &crtcs)
375{
376 if (connectors.isEmpty() || crtcs.isEmpty()) {
377 if (m_pipelines.isEmpty()) {
378 // nothing to do
380 }
381 // remaining connectors can't be powered
382 for (const auto &conn : std::as_const(connectors)) {
383 qCWarning(KWIN_DRM) << "disabling connector" << conn->modelName() << "without a crtc";
384 conn->pipeline()->setCrtc(nullptr);
385 }
386 return testPipelines();
387 }
388 auto connector = connectors.takeFirst();
389 auto pipeline = connector->pipeline();
390 if (!pipeline->enabled() || !connector->isConnected()) {
391 // disabled pipelines don't need CRTCs
392 pipeline->setCrtc(nullptr);
393 return checkCrtcAssignment(connectors, crtcs);
394 }
395 DrmCrtc *currentCrtc = nullptr;
396 if (m_atomicModeSetting) {
397 // try the crtc that this connector is already connected to first
398 const uint32_t id = connector->crtcId.value();
399 auto it = std::find_if(crtcs.begin(), crtcs.end(), [id](const auto &crtc) {
400 return id == crtc->id();
401 });
402 if (it != crtcs.end()) {
403 currentCrtc = *it;
404 auto crtcsLeft = crtcs;
405 crtcsLeft.removeOne(currentCrtc);
406 pipeline->setCrtc(currentCrtc);
407 do {
408 DrmPipeline::Error err = checkCrtcAssignment(connectors, crtcsLeft);
410 return err;
411 }
412 } while (pipeline->pruneModifier());
413 }
414 }
415 for (const auto &crtc : std::as_const(crtcs)) {
416 if (connector->isCrtcSupported(crtc) && crtc != currentCrtc) {
417 auto crtcsLeft = crtcs;
418 crtcsLeft.removeOne(crtc);
419 pipeline->setCrtc(crtc);
420 do {
421 DrmPipeline::Error err = checkCrtcAssignment(connectors, crtcsLeft);
423 return err;
424 }
425 } while (pipeline->pruneModifier());
426 }
427 }
429}
430
432{
433 QList<DrmConnector *> connectors;
434 QList<DrmCrtc *> crtcs;
435 // only change resources that aren't currently leased away
436 for (const auto &conn : m_connectors) {
437 bool isLeased = std::any_of(m_drmOutputs.cbegin(), m_drmOutputs.cend(), [&conn](const auto output) {
438 return output->lease() && output->pipeline()->connector() == conn.get();
439 });
440 if (!isLeased) {
441 connectors.push_back(conn.get());
442 }
443 }
444 for (const auto &crtc : m_crtcs) {
445 bool isLeased = std::any_of(m_drmOutputs.cbegin(), m_drmOutputs.cend(), [&crtc](const auto output) {
446 return output->lease() && output->pipeline()->crtc() == crtc.get();
447 });
448 if (!isLeased) {
449 crtcs.push_back(crtc.get());
450 }
451 }
452 if (m_atomicModeSetting) {
453 // sort outputs by being already connected (to any CRTC) so that already working outputs get preferred
454 std::sort(connectors.begin(), connectors.end(), [](auto c1, auto c2) {
455 return c1->crtcId.value() > c2->crtcId.value();
456 });
457 }
458 return checkCrtcAssignment(connectors, crtcs);
459}
460
461DrmPipeline::Error DrmGpu::testPipelines()
462{
463 QList<DrmPipeline *> inactivePipelines;
464 std::copy_if(m_pipelines.constBegin(), m_pipelines.constEnd(), std::back_inserter(inactivePipelines), [](const auto pipeline) {
465 return pipeline->enabled() && !pipeline->active();
466 });
468 if (!inactivePipelines.isEmpty() && test == DrmPipeline::Error::None) {
469 // ensure that pipelines that are set as enabled but currently inactive
470 // still work when they need to be set active again
471 for (const auto pipeline : std::as_const(inactivePipelines)) {
472 pipeline->setActive(true);
473 }
475 for (const auto pipeline : std::as_const(inactivePipelines)) {
476 pipeline->setActive(false);
477 }
478 }
479 return test;
480}
481
482DrmOutput *DrmGpu::findOutput(quint32 connector)
483{
484 auto it = std::find_if(m_drmOutputs.constBegin(), m_drmOutputs.constEnd(), [connector](DrmOutput *o) {
485 return o->connector()->id() == connector;
486 });
487 if (it != m_drmOutputs.constEnd()) {
488 return *it;
489 }
490 return nullptr;
491}
492
494{
495 m_socketNotifier->setEnabled(false);
496 while (true) {
497 const bool idle = std::all_of(m_drmOutputs.constBegin(), m_drmOutputs.constEnd(), [](DrmOutput *output) {
498 return !output->pipeline()->pageflipsPending();
499 });
500 if (idle) {
501 break;
502 }
503 pollfd pfds[1];
504 pfds[0].fd = m_fd;
505 pfds[0].events = POLLIN;
506
507 const int ready = poll(pfds, 1, 30000);
508 if (ready < 0) {
509 if (errno != EINTR) {
510 qCWarning(KWIN_DRM) << Q_FUNC_INFO << "poll() failed:" << strerror(errno);
511 break;
512 }
513 } else if (ready == 0) {
514 qCWarning(KWIN_DRM) << "No drm events for gpu" << m_devNode << "within last 30 seconds";
515 break;
516 } else {
517 dispatchEvents();
518 }
519 };
520 m_socketNotifier->setEnabled(true);
521}
522
523static std::chrono::nanoseconds convertTimestamp(const timespec &timestamp)
524{
525 return std::chrono::seconds(timestamp.tv_sec) + std::chrono::nanoseconds(timestamp.tv_nsec);
526}
527
528static std::chrono::nanoseconds convertTimestamp(clockid_t sourceClock, clockid_t targetClock,
529 const timespec &timestamp)
530{
531 if (sourceClock == targetClock) {
532 return convertTimestamp(timestamp);
533 }
534
535 timespec sourceCurrentTime = {};
536 timespec targetCurrentTime = {};
537
538 clock_gettime(sourceClock, &sourceCurrentTime);
539 clock_gettime(targetClock, &targetCurrentTime);
540
541 const auto delta = convertTimestamp(sourceCurrentTime) - convertTimestamp(timestamp);
542 return convertTimestamp(targetCurrentTime) - delta;
543}
544
545void DrmGpu::pageFlipHandler(int fd, unsigned int sequence, unsigned int sec, unsigned int usec, unsigned int crtc_id, void *user_data)
546{
547 const auto commit = static_cast<DrmCommit *>(user_data);
548 const auto gpu = commit->gpu();
549
550 // The static_cast<> here are for a 32-bit environment where
551 // sizeof(time_t) == sizeof(unsigned int) == 4 . Putting @p sec
552 // into a time_t cuts off the most-significant bit (after the
553 // year 2038), similarly long can't hold all the bits of an
554 // unsigned multiplication.
555 std::chrono::nanoseconds timestamp = convertTimestamp(gpu->presentationClock(), CLOCK_MONOTONIC,
556 {static_cast<time_t>(sec), static_cast<long>(usec * 1000)});
557 if (timestamp == std::chrono::nanoseconds::zero()) {
558 qCDebug(KWIN_DRM, "Got invalid timestamp (sec: %u, usec: %u) on gpu %s",
559 sec, usec, qPrintable(gpu->devNode()));
560 timestamp = std::chrono::steady_clock::now().time_since_epoch();
561 }
562 commit->pageFlipped(timestamp);
563}
564
565void DrmGpu::dispatchEvents()
566{
567 drmEventContext context = {};
568 context.version = 3;
569 context.page_flip_handler2 = pageFlipHandler;
570 drmHandleEvent(m_fd, &context);
571}
572
573void DrmGpu::removeOutput(DrmOutput *output)
574{
575 qCDebug(KWIN_DRM) << "Removing output" << output;
576 m_pipelines.removeOne(output->pipeline());
577 output->pipeline()->setLayers(nullptr, nullptr);
578 m_drmOutputs.removeOne(output);
579 Q_EMIT outputRemoved(output);
580 output->unref();
581 // force a modeset to make sure unused objects are cleaned up
582 m_forceModeset = true;
583}
584
586{
587 return m_platform;
588}
589
590const QList<DrmPipeline *> DrmGpu::pipelines() const
591{
592 return m_pipelines;
593}
594
595DrmVirtualOutput *DrmGpu::createVirtualOutput(const QString &name, const QSize &size, double scale)
596{
597 auto output = new DrmVirtualOutput(name, this, size, scale);
598 m_virtualOutputs << output;
599 Q_EMIT outputAdded(output);
600 return output;
601}
602
604{
605 if (m_virtualOutputs.removeOne(output)) {
606 Q_EMIT outputRemoved(output);
607 output->unref();
608 }
609}
610
611std::unique_ptr<DrmLease> DrmGpu::leaseOutputs(const QList<DrmOutput *> &outputs)
612{
613 QList<uint32_t> objects;
614 for (DrmOutput *output : outputs) {
615 if (output->lease() || !output->addLeaseObjects(objects)) {
616 return nullptr;
617 }
618 }
619
620 uint32_t lesseeId;
621 FileDescriptor fd{drmModeCreateLease(m_fd, objects.constData(), objects.count(), 0, &lesseeId)};
622 if (!fd.isValid()) {
623 qCWarning(KWIN_DRM) << "Could not create DRM lease!" << strerror(errno);
624 qCWarning(KWIN_DRM) << "Tried to lease the following" << objects.count() << "resources:";
625 for (const auto &res : std::as_const(objects)) {
626 qCWarning(KWIN_DRM) << res;
627 }
628 return nullptr;
629 } else {
630 qCDebug(KWIN_DRM) << "Created lease for" << objects.count() << "resources:";
631 for (const auto &res : std::as_const(objects)) {
632 qCDebug(KWIN_DRM) << res;
633 }
634 return std::make_unique<DrmLease>(this, std::move(fd), lesseeId, outputs);
635 }
636}
637
638QList<DrmVirtualOutput *> DrmGpu::virtualOutputs() const
639{
640 return m_virtualOutputs;
641}
642
643QList<DrmOutput *> DrmGpu::drmOutputs() const
644{
645 return m_drmOutputs;
646}
647
648int DrmGpu::fd() const
649{
650 return m_fd;
651}
652
653dev_t DrmGpu::deviceId() const
654{
655 return m_deviceId;
656}
657
659{
660 return m_atomicModeSetting;
661}
662
663QString DrmGpu::devNode() const
664{
665 return m_devNode;
666}
667
668gbm_device *DrmGpu::gbmDevice() const
669{
670 return m_gbmDevice;
671}
672
674{
675 return m_eglDisplay.get();
676}
677
678void DrmGpu::setEglDisplay(std::unique_ptr<EglDisplay> &&display)
679{
680 m_eglDisplay = std::move(display);
681}
682
684{
685 return m_addFB2ModifiersSupported;
686}
687
689{
690 return m_asyncPageflipSupported;
691}
692
693bool DrmGpu::isI915() const
694{
695 return m_isI915;
696}
697
699{
700 return m_isNVidia;
701}
702
704{
705 return m_isRemoved;
706}
707
709{
710 m_isRemoved = true;
711}
712
713void DrmGpu::setActive(bool active)
714{
715 if (m_isActive != active) {
716 m_isActive = active;
717 if (active) {
718 for (const auto &output : std::as_const(m_drmOutputs)) {
719 output->renderLoop()->uninhibit();
720 }
721 // while the session was inactive, the output list may have changed
722 m_platform->updateOutputs();
723 for (const auto &output : std::as_const(m_drmOutputs)) {
724 // force a modeset with legacy, we can't reliably know if one is needed
725 if (!atomicModeSetting()) {
726 output->pipeline()->forceLegacyModeset();
727 }
728 }
729 } else {
730 for (const auto &output : std::as_const(m_drmOutputs)) {
731 output->renderLoop()->inhibit();
732 }
733 }
734 Q_EMIT activeChanged(active);
735 }
736}
737
739{
740 return m_isActive;
741}
742
744{
745 return m_forceModeset || std::any_of(m_pipelines.constBegin(), m_pipelines.constEnd(), [](const auto &pipeline) {
746 return pipeline->needsModeset();
747 });
748}
749
751{
752 auto pipelines = m_pipelines;
753 for (const auto &output : std::as_const(m_drmOutputs)) {
754 if (output->lease()) {
755 pipelines.removeOne(output->pipeline());
756 }
757 }
758 bool presentPendingForAll = std::all_of(pipelines.constBegin(), pipelines.constEnd(), [](const auto &pipeline) {
759 return pipeline->modesetPresentPending() || !pipeline->activePending();
760 });
761 if (!presentPendingForAll) {
762 // commit only once all pipelines are ready for presentation
763 return true;
764 }
765 // make sure there's no pending pageflips
766 waitIdle();
768 for (DrmPipeline *pipeline : std::as_const(pipelines)) {
769 if (pipeline->modesetPresentPending()) {
770 pipeline->resetModesetPresentPending();
771 if (err != DrmPipeline::Error::None) {
772 pipeline->output()->frameFailed();
773 }
774 }
775 }
776 m_forceModeset = false;
777 if (err == DrmPipeline::Error::None) {
778 return true;
779 } else {
781 QTimer::singleShot(0, m_platform, &DrmBackend::updateOutputs);
782 }
783 return false;
784 }
785}
786
787QList<DrmObject *> DrmGpu::unusedObjects() const
788{
789 QList<DrmObject *> ret = m_allObjects;
790 for (const auto &pipeline : m_pipelines) {
791 ret.removeOne(pipeline->connector());
792 if (pipeline->crtc()) {
793 ret.removeOne(pipeline->crtc());
794 ret.removeOne(pipeline->crtc()->primaryPlane());
795 ret.removeOne(pipeline->crtc()->cursorPlane());
796 }
797 }
798 return ret;
799}
800
802{
803 return m_cursorSize;
804}
805
807{
808 for (const auto &plane : std::as_const(m_planes)) {
809 plane->releaseCurrentBuffer();
810 }
811 for (const auto &crtc : std::as_const(m_crtcs)) {
812 crtc->releaseCurrentBuffer();
813 }
814 for (const auto &pipeline : std::as_const(m_pipelines)) {
815 pipeline->primaryLayer()->releaseBuffers();
816 pipeline->cursorLayer()->releaseBuffers();
817 }
818 for (const auto &output : std::as_const(m_virtualOutputs)) {
819 output->primaryLayer()->releaseBuffers();
820 }
821}
822
824{
825 for (const auto &pipeline : std::as_const(m_pipelines)) {
826 pipeline->setLayers(m_platform->renderBackend()->createPrimaryLayer(pipeline), m_platform->renderBackend()->createCursorLayer(pipeline));
827 pipeline->applyPendingChanges();
828 }
829 for (const auto &output : std::as_const(m_virtualOutputs)) {
830 output->recreateSurface();
831 }
832}
833
835{
836 return m_allocator.get();
837}
838
839std::shared_ptr<DrmFramebuffer> DrmGpu::importBuffer(GraphicsBuffer *buffer, FileDescriptor &&readFence)
840{
841 const DmaBufAttributes *attributes = buffer->dmabufAttributes();
842 if (Q_UNLIKELY(!attributes)) {
843 return nullptr;
844 }
845
846 uint32_t handles[] = {0, 0, 0, 0};
847 auto cleanup = qScopeGuard([this, &handles]() {
848 for (int i = 0; i < 4; ++i) {
849 if (handles[i] == 0) {
850 continue;
851 }
852 bool closed = false;
853 for (int j = 0; j < i; ++j) {
854 if (handles[i] == handles[j]) {
855 closed = true;
856 break;
857 }
858 }
859 if (closed) {
860 continue;
861 }
862 drmCloseBufferHandle(m_fd, handles[i]);
863 }
864 });
865 for (int i = 0; i < attributes->planeCount; ++i) {
866 if (drmPrimeFDToHandle(m_fd, attributes->fd[i].get(), &handles[i]) != 0) {
867 qCWarning(KWIN_DRM) << "drmPrimeFDToHandle() failed";
868 return nullptr;
869 }
870 }
871
872 uint32_t framebufferId = 0;
873 int ret;
874 if (addFB2ModifiersSupported() && attributes->modifier != DRM_FORMAT_MOD_INVALID) {
875 uint64_t modifier[4] = {0, 0, 0, 0};
876 for (int i = 0; i < attributes->planeCount; ++i) {
877 modifier[i] = attributes->modifier;
878 }
880 attributes->width,
881 attributes->height,
882 attributes->format,
883 handles,
884 attributes->pitch.data(),
885 attributes->offset.data(),
886 modifier,
887 &framebufferId,
888 DRM_MODE_FB_MODIFIERS);
889 } else {
890 ret = drmModeAddFB2(m_fd,
891 attributes->width,
892 attributes->height,
893 attributes->format,
894 handles,
895 attributes->pitch.data(),
896 attributes->offset.data(),
897 &framebufferId,
898 0);
899 if (ret == EOPNOTSUPP && attributes->planeCount == 1) {
900 ret = drmModeAddFB(m_fd,
901 attributes->width,
902 attributes->height,
903 24, 32,
904 attributes->pitch[0],
905 handles[0],
906 &framebufferId);
907 }
908 }
909
910 if (ret != 0) {
911 return nullptr;
912 }
913
914 return std::make_shared<DrmFramebuffer>(this, framebufferId, buffer, std::move(readFence));
915}
916
917DrmLease::DrmLease(DrmGpu *gpu, FileDescriptor &&fd, uint32_t lesseeId, const QList<DrmOutput *> &outputs)
918 : m_gpu(gpu)
919 , m_fd(std::move(fd))
920 , m_lesseeId(lesseeId)
921 , m_outputs(outputs)
922{
923 for (const auto output : m_outputs) {
924 output->leased(this);
925 }
926}
927
929{
930 qCDebug(KWIN_DRM, "Revoking lease with leaseID %d", m_lesseeId);
931 drmModeRevokeLease(m_gpu->fd(), m_lesseeId);
932 for (const auto &output : m_outputs) {
933 output->leaseEnded();
934 }
935}
936
938{
939 return m_fd;
940}
941
942uint32_t DrmLease::lesseeId() const
943{
944 return m_lesseeId;
945}
946}
947
948#include "moc_drm_gpu.cpp"
Session * session() const override
DrmRenderBackend * renderBackend() const
bool isNonDesktop() const
bool isConnected() const
DrmPipeline * pipeline() const
QString modelName() const
void waitIdle()
Definition drm_gpu.cpp:493
bool asyncPageflipSupported() const
Definition drm_gpu.cpp:688
std::unique_ptr< DrmLease > leaseOutputs(const QList< DrmOutput * > &outputs)
Definition drm_gpu.cpp:611
void setActive(bool active)
Definition drm_gpu.cpp:713
QList< DrmOutput * > drmOutputs() const
Definition drm_gpu.cpp:643
void releaseBuffers()
Definition drm_gpu.cpp:806
void outputRemoved(DrmAbstractOutput *output)
void setEglDisplay(std::unique_ptr< EglDisplay > &&display)
Definition drm_gpu.cpp:678
DrmVirtualOutput * createVirtualOutput(const QString &name, const QSize &size, double scale)
Definition drm_gpu.cpp:595
EglDisplay * eglDisplay() const
Definition drm_gpu.cpp:673
void outputAdded(DrmAbstractOutput *output)
bool isNVidia() const
Definition drm_gpu.cpp:698
FileDescriptor createNonMasterFd() const
Definition drm_gpu.cpp:127
bool isRemoved() const
Definition drm_gpu.cpp:703
bool addFB2ModifiersSupported() const
Definition drm_gpu.cpp:683
const QList< DrmPipeline * > pipelines() const
Definition drm_gpu.cpp:590
clockid_t presentationClock() const
Definition drm_gpu.cpp:144
bool isActive() const
Definition drm_gpu.cpp:738
void setRemoved()
Definition drm_gpu.cpp:708
bool needsModeset() const
Definition drm_gpu.cpp:743
int fd() const
Definition drm_gpu.cpp:648
bool maybeModeset()
Definition drm_gpu.cpp:750
void recreateSurfaces()
Definition drm_gpu.cpp:823
QString devNode() const
Definition drm_gpu.cpp:663
DrmBackend * platform() const
Definition drm_gpu.cpp:585
std::shared_ptr< DrmFramebuffer > importBuffer(GraphicsBuffer *buffer, FileDescriptor &&explicitFence)
Definition drm_gpu.cpp:839
void activeChanged(bool active)
GraphicsBufferAllocator * graphicsBufferAllocator() const
Definition drm_gpu.cpp:834
DrmGpu(DrmBackend *backend, const QString &devNode, int fd, dev_t deviceId)
Definition drm_gpu.cpp:47
QSize cursorSize() const
Definition drm_gpu.cpp:801
void removeOutputs()
Definition drm_gpu.cpp:362
DrmPipeline::Error testPendingConfiguration()
Definition drm_gpu.cpp:431
dev_t deviceId() const
Definition drm_gpu.cpp:653
void removeVirtualOutput(DrmVirtualOutput *output)
Definition drm_gpu.cpp:603
QList< DrmVirtualOutput * > virtualOutputs() const
Definition drm_gpu.cpp:638
bool isI915() const
Definition drm_gpu.cpp:693
bool updateOutputs()
Definition drm_gpu.cpp:239
gbm_device * gbmDevice() const
Definition drm_gpu.cpp:668
bool atomicModeSetting() const
Definition drm_gpu.cpp:658
FileDescriptor & fd()
Definition drm_gpu.cpp:937
uint32_t lesseeId() const
Definition drm_gpu.cpp:942
DrmLease(DrmGpu *gpu, FileDescriptor &&fd, uint32_t lesseeId, const QList< DrmOutput * > &outputs)
Definition drm_gpu.cpp:917
uint32_t id() const
static Error commitPipelines(const QList< DrmPipeline * > &pipelines, CommitMode mode, const QList< DrmObject * > &unusedObjects={})
virtual std::shared_ptr< DrmPipelineLayer > createCursorLayer(DrmPipeline *pipeline)=0
virtual std::shared_ptr< DrmPipelineLayer > createPrimaryLayer(DrmPipeline *pipeline)=0
virtual const DmaBufAttributes * dmabufAttributes() const
void unref()
Definition output.cpp:350
virtual void closeRestricted(int fileDescriptor)=0
#define DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT
Definition drm_gpu.cpp:41
int drmModeCreateLease(int fd, const uint32_t *objects, int num_objects, int flags, uint32_t *lessee_id)
drmModePlaneResPtr drmModeGetPlaneResources(int fd)
Definition mock_drm.cpp:689
int drmHandleEvent(int fd, drmEventContextPtr evctx)
Definition mock_drm.cpp:327
int drmModeAddFB(int fd, uint32_t width, uint32_t height, uint8_t depth, uint8_t bpp, uint32_t pitch, uint32_t bo_handle, uint32_t *buf_id)
Definition mock_drm.cpp:412
drmModeResPtr drmModeGetResources(int fd)
Definition mock_drm.cpp:370
int drmGetCap(int fd, uint64_t capability, uint64_t *value)
Definition mock_drm.cpp:316
int drmModeAddFB2(int fd, uint32_t width, uint32_t height, uint32_t pixel_format, const uint32_t bo_handles[4], const uint32_t pitches[4], const uint32_t offsets[4], uint32_t *buf_id, uint32_t flags)
Definition mock_drm.cpp:422
int drmModeRevokeLease(int fd, uint32_t lessee_id)
drmModeLesseeListPtr drmModeListLessees(int fd)
drmVersionPtr drmGetVersion(int fd)
Definition mock_drm.cpp:287
int drmSetClientCap(int fd, uint64_t capability, uint64_t value)
Definition mock_drm.cpp:303
drmModePlanePtr drmModeGetPlane(int fd, uint32_t plane_id)
Definition mock_drm.cpp:702
int drmModeAddFB2WithModifiers(int fd, uint32_t width, uint32_t height, uint32_t pixel_format, const uint32_t bo_handles[4], const uint32_t pitches[4], const uint32_t offsets[4], const uint64_t modifier[4], uint32_t *buf_id, uint32_t flags)
Definition mock_drm.cpp:433
constexpr int version
std::unique_ptr< T, DrmDeleter< T > > DrmUniquePtr
std::array< uint32_t, 4 > offset
std::array< uint32_t, 4 > pitch
std::array< FileDescriptor, 4 > fd