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 ×tamp) 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 ×tamp) 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 }