File indexing completed on 2025-04-20 10:57:35

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2020 Xaver Hugl <xaver.hugl@gmail.com>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "drm_gpu.h"
0010 
0011 #include <config-kwin.h>
0012 
0013 #include "abstract_egl_backend.h"
0014 #include "core/renderloop_p.h"
0015 #include "core/session.h"
0016 #include "drm_backend.h"
0017 #include "drm_connector.h"
0018 #include "drm_crtc.h"
0019 #include "drm_egl_backend.h"
0020 #include "drm_layer.h"
0021 #include "drm_logging.h"
0022 #include "drm_output.h"
0023 #include "drm_pipeline.h"
0024 #include "drm_plane.h"
0025 #include "drm_virtual_output.h"
0026 #include "gbm_dmabuf.h"
0027 // system
0028 #include <algorithm>
0029 #include <errno.h>
0030 #include <fcntl.h>
0031 #include <poll.h>
0032 #include <unistd.h>
0033 // drm
0034 #include <drm_fourcc.h>
0035 #include <gbm.h>
0036 #include <libdrm/drm_mode.h>
0037 #include <xf86drm.h>
0038 #include <xf86drmMode.h>
0039 
0040 namespace KWin
0041 {
0042 
0043 DrmGpu::DrmGpu(DrmBackend *backend, const QString &devNode, int fd, dev_t deviceId)
0044     : m_fd(fd)
0045     , m_deviceId(deviceId)
0046     , m_devNode(devNode)
0047     , m_atomicModeSetting(false)
0048     , m_gbmDevice(nullptr)
0049     , m_platform(backend)
0050 {
0051     uint64_t capability = 0;
0052 
0053     if (drmGetCap(fd, DRM_CAP_CURSOR_WIDTH, &capability) == 0) {
0054         m_cursorSize.setWidth(capability);
0055     } else {
0056         m_cursorSize.setWidth(64);
0057     }
0058 
0059     if (drmGetCap(fd, DRM_CAP_CURSOR_HEIGHT, &capability) == 0) {
0060         m_cursorSize.setHeight(capability);
0061     } else {
0062         m_cursorSize.setHeight(64);
0063     }
0064 
0065     int ret = drmGetCap(fd, DRM_CAP_TIMESTAMP_MONOTONIC, &capability);
0066     if (ret == 0 && capability == 1) {
0067         m_presentationClock = CLOCK_MONOTONIC;
0068     } else {
0069         m_presentationClock = CLOCK_REALTIME;
0070     }
0071 
0072     m_addFB2ModifiersSupported = drmGetCap(fd, DRM_CAP_ADDFB2_MODIFIERS, &capability) == 0 && capability == 1;
0073     qCDebug(KWIN_DRM) << "drmModeAddFB2WithModifiers is" << (m_addFB2ModifiersSupported ? "supported" : "not supported") << "on GPU" << m_devNode;
0074 
0075     // find out what driver this kms device is using
0076     DrmUniquePtr<drmVersion> version(drmGetVersion(fd));
0077     m_isNVidia = strstr(version->name, "nvidia-drm");
0078     m_isVirtualMachine = strstr(version->name, "virtio") || strstr(version->name, "qxl")
0079         || strstr(version->name, "vmwgfx") || strstr(version->name, "vboxvideo");
0080     m_gbmDevice = gbm_create_device(m_fd);
0081 
0082     m_socketNotifier = std::make_unique<QSocketNotifier>(fd, QSocketNotifier::Read);
0083     connect(m_socketNotifier.get(), &QSocketNotifier::activated, this, &DrmGpu::dispatchEvents);
0084 
0085     initDrmResources();
0086 
0087     if (m_atomicModeSetting == false) {
0088         // only supported with legacy
0089         m_asyncPageflipSupported = drmGetCap(fd, DRM_CAP_ASYNC_PAGE_FLIP, &capability) == 0 && capability == 1;
0090     }
0091 }
0092 
0093 DrmGpu::~DrmGpu()
0094 {
0095     removeOutputs();
0096     if (m_eglDisplay != EGL_NO_DISPLAY) {
0097         eglTerminate(m_eglDisplay);
0098     }
0099     m_crtcs.clear();
0100     m_connectors.clear();
0101     m_planes.clear();
0102     m_socketNotifier.reset();
0103     if (m_gbmDevice) {
0104         gbm_device_destroy(m_gbmDevice);
0105     }
0106     m_platform->session()->closeRestricted(m_fd);
0107 }
0108 
0109 FileDescriptor DrmGpu::createNonMasterFd() const
0110 {
0111     char *path = drmGetDeviceNameFromFd2(m_fd);
0112     FileDescriptor fd{open(path, O_RDWR | O_CLOEXEC)};
0113     if (!fd.isValid()) {
0114         qCWarning(KWIN_DRM) << "Could not open DRM fd for leasing!" << strerror(errno);
0115     } else {
0116         if (drmIsMaster(fd.get())) {
0117             if (drmDropMaster(fd.get()) != 0) {
0118                 qCWarning(KWIN_DRM) << "Could not create a non-master DRM fd for leasing!" << strerror(errno);
0119                 return FileDescriptor{};
0120             }
0121         }
0122     }
0123     return fd;
0124 }
0125 
0126 clockid_t DrmGpu::presentationClock() const
0127 {
0128     return m_presentationClock;
0129 }
0130 
0131 void DrmGpu::initDrmResources()
0132 {
0133     // try atomic mode setting
0134     bool isEnvVarSet = false;
0135     bool noAMS = qEnvironmentVariableIntValue("KWIN_DRM_NO_AMS", &isEnvVarSet) != 0 && isEnvVarSet;
0136     if (m_isVirtualMachine && !isEnvVarSet) {
0137         qCWarning(KWIN_DRM, "Atomic Mode Setting disabled on GPU %s because of cursor offset issues in virtual machines", qPrintable(m_devNode));
0138     } else if (noAMS) {
0139         qCWarning(KWIN_DRM) << "Atomic Mode Setting requested off via environment variable. Using legacy mode on GPU" << m_devNode;
0140     } else if (drmSetClientCap(m_fd, DRM_CLIENT_CAP_ATOMIC, 1) != 0) {
0141         qCWarning(KWIN_DRM) << "drmSetClientCap for Atomic Mode Setting failed. Using legacy mode on GPU" << m_devNode;
0142     } else {
0143         DrmUniquePtr<drmModePlaneRes> planeResources(drmModeGetPlaneResources(m_fd));
0144         if (planeResources) {
0145             qCDebug(KWIN_DRM) << "Using Atomic Mode Setting on gpu" << m_devNode;
0146             qCDebug(KWIN_DRM) << "Number of planes on GPU" << m_devNode << ":" << planeResources->count_planes;
0147             // create the plane objects
0148             for (unsigned int i = 0; i < planeResources->count_planes; ++i) {
0149                 DrmUniquePtr<drmModePlane> kplane(drmModeGetPlane(m_fd, planeResources->planes[i]));
0150                 auto plane = std::make_unique<DrmPlane>(this, kplane->plane_id);
0151                 if (plane->init()) {
0152                     m_allObjects << plane.get();
0153                     m_planes.push_back(std::move(plane));
0154                 }
0155             }
0156             if (m_planes.empty()) {
0157                 qCWarning(KWIN_DRM) << "Failed to create any plane. Falling back to legacy mode on GPU " << m_devNode;
0158             }
0159         } else {
0160             qCWarning(KWIN_DRM) << "Failed to get plane resources. Falling back to legacy mode on GPU " << m_devNode;
0161         }
0162     }
0163     m_atomicModeSetting = !m_planes.empty();
0164 
0165     DrmUniquePtr<drmModeRes> resources(drmModeGetResources(m_fd));
0166     if (!resources) {
0167         qCCritical(KWIN_DRM) << "drmModeGetResources for getting CRTCs failed on GPU" << m_devNode;
0168         return;
0169     }
0170     QVector<DrmPlane *> assignedPlanes;
0171     for (int i = 0; i < resources->count_crtcs; ++i) {
0172         uint32_t crtcId = resources->crtcs[i];
0173         QVector<DrmPlane *> primaryCandidates;
0174         QVector<DrmPlane *> cursorCandidates;
0175         for (const auto &plane : m_planes) {
0176             if (plane->isCrtcSupported(i) && !assignedPlanes.contains(plane.get())) {
0177                 if (plane->type() == DrmPlane::TypeIndex::Primary) {
0178                     primaryCandidates.push_back(plane.get());
0179                 } else if (plane->type() == DrmPlane::TypeIndex::Cursor) {
0180                     cursorCandidates.push_back(plane.get());
0181                 }
0182             }
0183         }
0184         if (m_atomicModeSetting && primaryCandidates.empty()) {
0185             qCWarning(KWIN_DRM) << "Could not find a suitable primary plane for crtc" << resources->crtcs[i];
0186             continue;
0187         }
0188         const auto findBestPlane = [crtcId](const QVector<DrmPlane *> &list) {
0189             // if the plane is already used with this crtc, prefer it
0190             const auto connected = std::find_if(list.begin(), list.end(), [crtcId](DrmPlane *plane) {
0191                 return plane->getProp(DrmPlane::PropertyIndex::CrtcId)->pending() == crtcId;
0192             });
0193             if (connected != list.end()) {
0194                 return *connected;
0195             }
0196             // don't take away planes from other crtcs. The kernel currently rejects such commits
0197             const auto notconnected = std::find_if(list.begin(), list.end(), [](DrmPlane *plane) {
0198                 return plane->getProp(DrmPlane::PropertyIndex::CrtcId)->pending() == 0;
0199             });
0200             if (notconnected != list.end()) {
0201                 return *notconnected;
0202             }
0203             return list.empty() ? nullptr : list.front();
0204         };
0205         DrmPlane *primary = findBestPlane(primaryCandidates);
0206         DrmPlane *cursor = findBestPlane(cursorCandidates);
0207         assignedPlanes.push_back(primary);
0208         if (cursor) {
0209             assignedPlanes.push_back(cursor);
0210         }
0211         auto crtc = std::make_unique<DrmCrtc>(this, crtcId, i, primary, cursor);
0212         if (!crtc->init()) {
0213             continue;
0214         }
0215         m_allObjects << crtc.get();
0216         m_crtcs.push_back(std::move(crtc));
0217     }
0218 }
0219 
0220 bool DrmGpu::updateOutputs()
0221 {
0222     waitIdle();
0223     DrmUniquePtr<drmModeRes> resources(drmModeGetResources(m_fd));
0224     if (!resources) {
0225         qCWarning(KWIN_DRM) << "drmModeGetResources failed";
0226         return false;
0227     }
0228 
0229     // In principle these things are supposed to be detected through the wayland protocol.
0230     // In practice SteamVR doesn't always behave correctly
0231     DrmUniquePtr<drmModeLesseeListRes> lessees{drmModeListLessees(m_fd)};
0232     for (const auto &output : std::as_const(m_drmOutputs)) {
0233         if (output->lease()) {
0234             bool leaseActive = false;
0235             for (uint i = 0; i < lessees->count; i++) {
0236                 if (lessees->lessees[i] == output->lease()->lesseeId()) {
0237                     leaseActive = true;
0238                     break;
0239                 }
0240             }
0241             if (!leaseActive) {
0242                 Q_EMIT output->lease()->revokeRequested();
0243             }
0244         }
0245     }
0246 
0247     // check for added and removed connectors
0248     QVector<DrmConnector *> existing;
0249     QVector<DrmOutput *> addedOutputs;
0250     for (int i = 0; i < resources->count_connectors; ++i) {
0251         const uint32_t currentConnector = resources->connectors[i];
0252         const auto it = std::find_if(m_connectors.begin(), m_connectors.end(), [currentConnector](const auto &connector) {
0253             return connector->id() == currentConnector;
0254         });
0255         if (it == m_connectors.end()) {
0256             auto conn = std::make_shared<DrmConnector>(this, currentConnector);
0257             if (!conn->init()) {
0258                 continue;
0259             }
0260             existing.push_back(conn.get());
0261             m_allObjects.push_back(conn.get());
0262             m_connectors.push_back(std::move(conn));
0263         } else {
0264             (*it)->updateProperties();
0265             existing.push_back(it->get());
0266         }
0267     }
0268     for (auto it = m_connectors.begin(); it != m_connectors.end();) {
0269         DrmConnector *conn = it->get();
0270         const auto output = findOutput(conn->id());
0271         const bool stillExists = existing.contains(conn);
0272         if (!stillExists || !conn->isConnected()) {
0273             if (output) {
0274                 removeOutput(output);
0275             }
0276             conn->disable();
0277         } else if (!output) {
0278             qCDebug(KWIN_DRM, "New %soutput on GPU %s: %s", conn->isNonDesktop() ? "non-desktop " : "", qPrintable(m_devNode), qPrintable(conn->modelName()));
0279             const auto pipeline = conn->pipeline();
0280             m_pipelines << pipeline;
0281             auto output = new DrmOutput(*it);
0282             m_drmOutputs << output;
0283             addedOutputs << output;
0284             Q_EMIT outputAdded(output);
0285             pipeline->setLayers(m_platform->renderBackend()->createPrimaryLayer(pipeline), m_platform->renderBackend()->createCursorLayer(pipeline));
0286             pipeline->setActive(!conn->isNonDesktop());
0287             pipeline->applyPendingChanges();
0288         }
0289         if (stillExists) {
0290             it++;
0291         } else {
0292             m_allObjects.removeOne(it->get());
0293             it = m_connectors.erase(it);
0294         }
0295     }
0296 
0297     // update crtc properties
0298     for (const auto &crtc : std::as_const(m_crtcs)) {
0299         crtc->updateProperties();
0300     }
0301     // update plane properties
0302     for (const auto &plane : std::as_const(m_planes)) {
0303         plane->updateProperties();
0304     }
0305     DrmPipeline::Error err = testPendingConfiguration();
0306     if (err == DrmPipeline::Error::None) {
0307         for (const auto &pipeline : std::as_const(m_pipelines)) {
0308             pipeline->applyPendingChanges();
0309             if (pipeline->output() && !pipeline->crtc()) {
0310                 pipeline->setEnable(false);
0311                 pipeline->output()->updateEnabled(false);
0312             }
0313         }
0314     } else if (err == DrmPipeline::Error::NoPermission) {
0315         for (const auto &pipeline : std::as_const(m_pipelines)) {
0316             pipeline->revertPendingChanges();
0317         }
0318         for (const auto &output : std::as_const(addedOutputs)) {
0319             removeOutput(output);
0320             const auto it = std::find_if(m_connectors.begin(), m_connectors.end(), [output](const auto &conn) {
0321                 return conn.get() == output->connector();
0322             });
0323             Q_ASSERT(it != m_connectors.end());
0324             m_allObjects.removeOne(it->get());
0325             m_connectors.erase(it);
0326         }
0327         QTimer::singleShot(50, m_platform, &DrmBackend::updateOutputs);
0328     } else {
0329         qCWarning(KWIN_DRM, "Failed to find a working setup for new outputs!");
0330         for (const auto &pipeline : std::as_const(m_pipelines)) {
0331             pipeline->revertPendingChanges();
0332         }
0333         for (const auto &output : std::as_const(addedOutputs)) {
0334             output->updateEnabled(false);
0335             output->pipeline()->setEnable(false);
0336             output->pipeline()->applyPendingChanges();
0337         }
0338     }
0339     return true;
0340 }
0341 
0342 void DrmGpu::removeOutputs()
0343 {
0344     waitIdle();
0345 
0346     const auto outputs = m_drmOutputs;
0347     for (const auto &output : outputs) {
0348         removeOutput(output);
0349     }
0350     const auto virtualOutputs = m_virtualOutputs;
0351     for (const auto &output : virtualOutputs) {
0352         removeVirtualOutput(output);
0353     }
0354 }
0355 
0356 DrmPipeline::Error DrmGpu::checkCrtcAssignment(QVector<DrmConnector *> connectors, const QVector<DrmCrtc *> &crtcs)
0357 {
0358     if (connectors.isEmpty() || crtcs.isEmpty()) {
0359         if (m_pipelines.isEmpty()) {
0360             // nothing to do
0361             return DrmPipeline::Error::None;
0362         }
0363         // remaining connectors can't be powered
0364         for (const auto &conn : std::as_const(connectors)) {
0365             qCWarning(KWIN_DRM) << "disabling connector" << conn->modelName() << "without a crtc";
0366             conn->pipeline()->setCrtc(nullptr);
0367         }
0368         return testPipelines();
0369     }
0370     auto connector = connectors.takeFirst();
0371     auto pipeline = connector->pipeline();
0372     if (!pipeline->enabled() || !connector->isConnected()) {
0373         // disabled pipelines don't need CRTCs
0374         pipeline->setCrtc(nullptr);
0375         return checkCrtcAssignment(connectors, crtcs);
0376     }
0377     DrmCrtc *currentCrtc = nullptr;
0378     if (m_atomicModeSetting) {
0379         // try the crtc that this connector is already connected to first
0380         uint32_t id = connector->getProp(DrmConnector::PropertyIndex::CrtcId)->pending();
0381         auto it = std::find_if(crtcs.begin(), crtcs.end(), [id](const auto &crtc) {
0382             return id == crtc->id();
0383         });
0384         if (it != crtcs.end()) {
0385             currentCrtc = *it;
0386             auto crtcsLeft = crtcs;
0387             crtcsLeft.removeOne(currentCrtc);
0388             pipeline->setCrtc(currentCrtc);
0389             do {
0390                 DrmPipeline::Error err = checkCrtcAssignment(connectors, crtcsLeft);
0391                 if (err == DrmPipeline::Error::None || err == DrmPipeline::Error::NoPermission || err == DrmPipeline::Error::FramePending) {
0392                     return err;
0393                 }
0394             } while (pipeline->pruneModifier());
0395         }
0396     }
0397     for (const auto &crtc : std::as_const(crtcs)) {
0398         if (connector->isCrtcSupported(crtc) && crtc != currentCrtc) {
0399             auto crtcsLeft = crtcs;
0400             crtcsLeft.removeOne(crtc);
0401             pipeline->setCrtc(crtc);
0402             do {
0403                 DrmPipeline::Error err = checkCrtcAssignment(connectors, crtcsLeft);
0404                 if (err == DrmPipeline::Error::None || err == DrmPipeline::Error::NoPermission || err == DrmPipeline::Error::FramePending) {
0405                     return err;
0406                 }
0407             } while (pipeline->pruneModifier());
0408         }
0409     }
0410     return DrmPipeline::Error::InvalidArguments;
0411 }
0412 
0413 DrmPipeline::Error DrmGpu::testPendingConfiguration()
0414 {
0415     QVector<DrmConnector *> connectors;
0416     QVector<DrmCrtc *> crtcs;
0417     // only change resources that aren't currently leased away
0418     for (const auto &conn : m_connectors) {
0419         bool isLeased = std::any_of(m_drmOutputs.cbegin(), m_drmOutputs.cend(), [&conn](const auto output) {
0420             return output->lease() && output->pipeline()->connector() == conn.get();
0421         });
0422         if (!isLeased) {
0423             connectors.push_back(conn.get());
0424         }
0425     }
0426     for (const auto &crtc : m_crtcs) {
0427         bool isLeased = std::any_of(m_drmOutputs.cbegin(), m_drmOutputs.cend(), [&crtc](const auto output) {
0428             return output->lease() && output->pipeline()->crtc() == crtc.get();
0429         });
0430         if (!isLeased) {
0431             crtcs.push_back(crtc.get());
0432         }
0433     }
0434     if (m_atomicModeSetting) {
0435         // sort outputs by being already connected (to any CRTC) so that already working outputs get preferred
0436         std::sort(connectors.begin(), connectors.end(), [](auto c1, auto c2) {
0437             return c1->getProp(DrmConnector::PropertyIndex::CrtcId)->current() > c2->getProp(DrmConnector::PropertyIndex::CrtcId)->current();
0438         });
0439     }
0440     DrmPipeline::Error err = checkCrtcAssignment(connectors, crtcs);
0441     if (err == DrmPipeline::Error::None || err == DrmPipeline::Error::NoPermission || err == DrmPipeline::Error::FramePending) {
0442         return err;
0443     } else {
0444         // try again without hw rotation
0445         bool hwRotationUsed = false;
0446         for (const auto &pipeline : std::as_const(m_pipelines)) {
0447             hwRotationUsed |= (pipeline->bufferOrientation() != DrmPlane::Transformations(DrmPlane::Transformation::Rotate0));
0448             pipeline->setBufferOrientation(DrmPlane::Transformation::Rotate0);
0449         }
0450         if (hwRotationUsed) {
0451             err = checkCrtcAssignment(connectors, crtcs);
0452         }
0453         return err;
0454     }
0455 }
0456 
0457 DrmPipeline::Error DrmGpu::testPipelines()
0458 {
0459     QVector<DrmPipeline *> inactivePipelines;
0460     std::copy_if(m_pipelines.constBegin(), m_pipelines.constEnd(), std::back_inserter(inactivePipelines), [](const auto pipeline) {
0461         return pipeline->enabled() && !pipeline->active();
0462     });
0463     DrmPipeline::Error test = DrmPipeline::commitPipelines(m_pipelines, DrmPipeline::CommitMode::TestAllowModeset, unusedObjects());
0464     if (!inactivePipelines.isEmpty() && test == DrmPipeline::Error::None) {
0465         // ensure that pipelines that are set as enabled but currently inactive
0466         // still work when they need to be set active again
0467         for (const auto pipeline : std::as_const(inactivePipelines)) {
0468             pipeline->setActive(true);
0469         }
0470         test = DrmPipeline::commitPipelines(m_pipelines, DrmPipeline::CommitMode::TestAllowModeset, unusedObjects());
0471         for (const auto pipeline : std::as_const(inactivePipelines)) {
0472             pipeline->setActive(false);
0473         }
0474     }
0475     return test;
0476 }
0477 
0478 DrmOutput *DrmGpu::findOutput(quint32 connector)
0479 {
0480     auto it = std::find_if(m_drmOutputs.constBegin(), m_drmOutputs.constEnd(), [connector](DrmOutput *o) {
0481         return o->connector()->id() == connector;
0482     });
0483     if (it != m_drmOutputs.constEnd()) {
0484         return *it;
0485     }
0486     return nullptr;
0487 }
0488 
0489 void DrmGpu::waitIdle()
0490 {
0491     m_socketNotifier->setEnabled(false);
0492     while (true) {
0493         const bool idle = std::all_of(m_drmOutputs.constBegin(), m_drmOutputs.constEnd(), [](DrmOutput *output) {
0494             return !output->pipeline()->pageflipPending();
0495         });
0496         if (idle) {
0497             break;
0498         }
0499         pollfd pfds[1];
0500         pfds[0].fd = m_fd;
0501         pfds[0].events = POLLIN;
0502 
0503         const int ready = poll(pfds, 1, 30000);
0504         if (ready < 0) {
0505             if (errno != EINTR) {
0506                 qCWarning(KWIN_DRM) << Q_FUNC_INFO << "poll() failed:" << strerror(errno);
0507                 break;
0508             }
0509         } else if (ready == 0) {
0510             qCWarning(KWIN_DRM) << "No drm events for gpu" << m_devNode << "within last 30 seconds";
0511             break;
0512         } else {
0513             dispatchEvents();
0514         }
0515     };
0516     m_socketNotifier->setEnabled(true);
0517 }
0518 
0519 static std::chrono::nanoseconds convertTimestamp(const timespec &timestamp)
0520 {
0521     return std::chrono::seconds(timestamp.tv_sec) + std::chrono::nanoseconds(timestamp.tv_nsec);
0522 }
0523 
0524 static std::chrono::nanoseconds convertTimestamp(clockid_t sourceClock, clockid_t targetClock,
0525                                                  const timespec &timestamp)
0526 {
0527     if (sourceClock == targetClock) {
0528         return convertTimestamp(timestamp);
0529     }
0530 
0531     timespec sourceCurrentTime = {};
0532     timespec targetCurrentTime = {};
0533 
0534     clock_gettime(sourceClock, &sourceCurrentTime);
0535     clock_gettime(targetClock, &targetCurrentTime);
0536 
0537     const auto delta = convertTimestamp(sourceCurrentTime) - convertTimestamp(timestamp);
0538     return convertTimestamp(targetCurrentTime) - delta;
0539 }
0540 
0541 void DrmGpu::pageFlipHandler(int fd, unsigned int sequence, unsigned int sec, unsigned int usec, unsigned int crtc_id, void *user_data)
0542 {
0543     DrmGpu *gpu = static_cast<DrmGpu *>(user_data);
0544 
0545     // The static_cast<> here are for a 32-bit environment where
0546     // sizeof(time_t) == sizeof(unsigned int) == 4 . Putting @p sec
0547     // into a time_t cuts off the most-significant bit (after the
0548     // year 2038), similarly long can't hold all the bits of an
0549     // unsigned multiplication.
0550     std::chrono::nanoseconds timestamp = convertTimestamp(gpu->presentationClock(), CLOCK_MONOTONIC,
0551                                                           {static_cast<time_t>(sec), static_cast<long>(usec * 1000)});
0552     if (timestamp == std::chrono::nanoseconds::zero()) {
0553         qCDebug(KWIN_DRM, "Got invalid timestamp (sec: %u, usec: %u) on gpu %s",
0554                 sec, usec, qPrintable(gpu->devNode()));
0555         timestamp = std::chrono::steady_clock::now().time_since_epoch();
0556     }
0557     const auto pipelines = gpu->pipelines();
0558     auto it = std::find_if(pipelines.begin(), pipelines.end(), [crtc_id](const auto &pipeline) {
0559         return pipeline->currentCrtc() && pipeline->currentCrtc()->id() == crtc_id;
0560     });
0561     if (it == pipelines.end()) {
0562         qCWarning(KWIN_DRM, "received invalid page flip event for crtc %u", crtc_id);
0563     } else {
0564         (*it)->pageFlipped(timestamp);
0565     }
0566 }
0567 
0568 void DrmGpu::dispatchEvents()
0569 {
0570     drmEventContext context = {};
0571     context.version = 3;
0572     context.page_flip_handler2 = pageFlipHandler;
0573     drmHandleEvent(m_fd, &context);
0574 }
0575 
0576 void DrmGpu::removeOutput(DrmOutput *output)
0577 {
0578     qCDebug(KWIN_DRM) << "Removing output" << output;
0579     m_pipelines.removeOne(output->pipeline());
0580     output->pipeline()->setLayers(nullptr, nullptr);
0581     m_drmOutputs.removeOne(output);
0582     Q_EMIT outputRemoved(output);
0583     output->unref();
0584 }
0585 
0586 DrmBackend *DrmGpu::platform() const
0587 {
0588     return m_platform;
0589 }
0590 
0591 const QVector<DrmPipeline *> DrmGpu::pipelines() const
0592 {
0593     return m_pipelines;
0594 }
0595 
0596 DrmVirtualOutput *DrmGpu::createVirtualOutput(const QString &name, const QSize &size, double scale)
0597 {
0598     auto output = new DrmVirtualOutput(name, this, size, scale);
0599     m_virtualOutputs << output;
0600     Q_EMIT outputAdded(output);
0601     return output;
0602 }
0603 
0604 void DrmGpu::removeVirtualOutput(DrmVirtualOutput *output)
0605 {
0606     if (m_virtualOutputs.removeOne(output)) {
0607         Q_EMIT outputRemoved(output);
0608         output->unref();
0609     }
0610 }
0611 
0612 std::unique_ptr<DrmLease> DrmGpu::leaseOutputs(const QVector<DrmOutput *> &outputs)
0613 {
0614     QVector<uint32_t> objects;
0615     for (DrmOutput *output : outputs) {
0616         if (output->lease() || !output->addLeaseObjects(objects)) {
0617             return nullptr;
0618         }
0619     }
0620 
0621     uint32_t lesseeId;
0622     FileDescriptor fd{drmModeCreateLease(m_fd, objects.constData(), objects.count(), 0, &lesseeId)};
0623     if (!fd.isValid()) {
0624         qCWarning(KWIN_DRM) << "Could not create DRM lease!" << strerror(errno);
0625         qCWarning(KWIN_DRM, "Tried to lease the following %d resources:", objects.count());
0626         for (const auto &res : std::as_const(objects)) {
0627             qCWarning(KWIN_DRM) << res;
0628         }
0629         return nullptr;
0630     } else {
0631         qCDebug(KWIN_DRM, "Created lease for %d resources:", objects.count());
0632         for (const auto &res : std::as_const(objects)) {
0633             qCDebug(KWIN_DRM) << res;
0634         }
0635         return std::make_unique<DrmLease>(this, std::move(fd), lesseeId, outputs);
0636     }
0637 }
0638 
0639 QVector<DrmVirtualOutput *> DrmGpu::virtualOutputs() const
0640 {
0641     return m_virtualOutputs;
0642 }
0643 
0644 QVector<DrmOutput *> DrmGpu::drmOutputs() const
0645 {
0646     return m_drmOutputs;
0647 }
0648 
0649 int DrmGpu::fd() const
0650 {
0651     return m_fd;
0652 }
0653 
0654 dev_t DrmGpu::deviceId() const
0655 {
0656     return m_deviceId;
0657 }
0658 
0659 bool DrmGpu::atomicModeSetting() const
0660 {
0661     return m_atomicModeSetting;
0662 }
0663 
0664 QString DrmGpu::devNode() const
0665 {
0666     return m_devNode;
0667 }
0668 
0669 gbm_device *DrmGpu::gbmDevice() const
0670 {
0671     return m_gbmDevice;
0672 }
0673 
0674 EGLDisplay DrmGpu::eglDisplay() const
0675 {
0676     return m_eglDisplay;
0677 }
0678 
0679 void DrmGpu::setEglDisplay(EGLDisplay display)
0680 {
0681     m_eglDisplay = display;
0682 }
0683 
0684 bool DrmGpu::addFB2ModifiersSupported() const
0685 {
0686     return m_addFB2ModifiersSupported;
0687 }
0688 
0689 bool DrmGpu::asyncPageflipSupported() const
0690 {
0691     return m_asyncPageflipSupported;
0692 }
0693 
0694 bool DrmGpu::isNVidia() const
0695 {
0696     return m_isNVidia;
0697 }
0698 
0699 bool DrmGpu::isRemoved() const
0700 {
0701     return m_isRemoved;
0702 }
0703 
0704 void DrmGpu::setRemoved()
0705 {
0706     m_isRemoved = true;
0707 }
0708 
0709 bool DrmGpu::needsModeset() const
0710 {
0711     return std::any_of(m_pipelines.constBegin(), m_pipelines.constEnd(), [](const auto &pipeline) {
0712         return pipeline->needsModeset();
0713     });
0714 }
0715 
0716 bool DrmGpu::maybeModeset()
0717 {
0718     auto pipelines = m_pipelines;
0719     for (const auto &output : std::as_const(m_drmOutputs)) {
0720         if (output->lease()) {
0721             pipelines.removeOne(output->pipeline());
0722         }
0723     }
0724     bool presentPendingForAll = std::all_of(pipelines.constBegin(), pipelines.constEnd(), [](const auto &pipeline) {
0725         return pipeline->modesetPresentPending() || !pipeline->activePending();
0726     });
0727     if (!presentPendingForAll) {
0728         // commit only once all pipelines are ready for presentation
0729         return true;
0730     }
0731     // make sure there's no pending pageflips
0732     waitIdle();
0733     const DrmPipeline::Error err = DrmPipeline::commitPipelines(pipelines, DrmPipeline::CommitMode::CommitModeset, unusedObjects());
0734     for (DrmPipeline *pipeline : std::as_const(pipelines)) {
0735         if (pipeline->modesetPresentPending()) {
0736             pipeline->resetModesetPresentPending();
0737             if (err != DrmPipeline::Error::None) {
0738                 pipeline->output()->frameFailed();
0739             }
0740         }
0741     }
0742     if (err == DrmPipeline::Error::None) {
0743         return true;
0744     } else {
0745         if (err != DrmPipeline::Error::FramePending) {
0746             QTimer::singleShot(0, m_platform, &DrmBackend::updateOutputs);
0747         }
0748         return false;
0749     }
0750 }
0751 
0752 QVector<DrmObject *> DrmGpu::unusedObjects() const
0753 {
0754     if (!m_atomicModeSetting) {
0755         return {};
0756     }
0757     QVector<DrmObject *> ret = m_allObjects;
0758     for (const auto &pipeline : m_pipelines) {
0759         ret.removeOne(pipeline->connector());
0760         if (pipeline->crtc()) {
0761             ret.removeOne(pipeline->crtc());
0762             ret.removeOne(pipeline->crtc()->primaryPlane());
0763             ret.removeOne(pipeline->crtc()->cursorPlane());
0764         }
0765     }
0766     return ret;
0767 }
0768 
0769 QSize DrmGpu::cursorSize() const
0770 {
0771     return m_cursorSize;
0772 }
0773 
0774 void DrmGpu::releaseBuffers()
0775 {
0776     for (const auto &plane : std::as_const(m_planes)) {
0777         plane->releaseBuffers();
0778     }
0779     for (const auto &crtc : std::as_const(m_crtcs)) {
0780         crtc->releaseBuffers();
0781     }
0782     for (const auto &pipeline : std::as_const(m_pipelines)) {
0783         pipeline->primaryLayer()->releaseBuffers();
0784         pipeline->cursorLayer()->releaseBuffers();
0785     }
0786     for (const auto &output : std::as_const(m_virtualOutputs)) {
0787         output->primaryLayer()->releaseBuffers();
0788     }
0789 }
0790 
0791 void DrmGpu::recreateSurfaces()
0792 {
0793     for (const auto &pipeline : std::as_const(m_pipelines)) {
0794         pipeline->setLayers(m_platform->renderBackend()->createPrimaryLayer(pipeline), m_platform->renderBackend()->createCursorLayer(pipeline));
0795         pipeline->applyPendingChanges();
0796     }
0797     for (const auto &output : std::as_const(m_virtualOutputs)) {
0798         output->recreateSurface();
0799     }
0800 }
0801 
0802 DrmLease::DrmLease(DrmGpu *gpu, FileDescriptor &&fd, uint32_t lesseeId, const QVector<DrmOutput *> &outputs)
0803     : m_gpu(gpu)
0804     , m_fd(std::move(fd))
0805     , m_lesseeId(lesseeId)
0806     , m_outputs(outputs)
0807 {
0808     for (const auto output : m_outputs) {
0809         output->leased(this);
0810     }
0811 }
0812 
0813 DrmLease::~DrmLease()
0814 {
0815     qCDebug(KWIN_DRM, "Revoking lease with leaseID %d", m_lesseeId);
0816     drmModeRevokeLease(m_gpu->fd(), m_lesseeId);
0817     for (const auto &output : m_outputs) {
0818         output->leaseEnded();
0819     }
0820 }
0821 
0822 FileDescriptor &DrmLease::fd()
0823 {
0824     return m_fd;
0825 }
0826 
0827 uint32_t DrmLease::lesseeId() const
0828 {
0829     return m_lesseeId;
0830 }
0831 }