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: 2015 Martin Gräßlin <mgraesslin@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "drm_output.h"
0010 #include "drm_backend.h"
0011 #include "drm_buffer.h"
0012 #include "drm_connector.h"
0013 #include "drm_crtc.h"
0014 #include "drm_gpu.h"
0015 #include "drm_pipeline.h"
0016 
0017 #include "core/outputconfiguration.h"
0018 #include "core/renderloop.h"
0019 #include "core/renderloop_p.h"
0020 #include "drm_dumb_buffer.h"
0021 #include "drm_dumb_swapchain.h"
0022 #include "drm_egl_backend.h"
0023 #include "drm_layer.h"
0024 #include "drm_logging.h"
0025 #include "kwinglutils.h"
0026 // Qt
0027 #include <QCryptographicHash>
0028 #include <QMatrix4x4>
0029 #include <QPainter>
0030 // c++
0031 #include <cerrno>
0032 // drm
0033 #include <drm_fourcc.h>
0034 #include <libdrm/drm_mode.h>
0035 #include <xf86drm.h>
0036 
0037 #include "composite.h"
0038 #include "core/renderlayer.h"
0039 #include "cursorsource.h"
0040 #include "scene/cursorscene.h"
0041 
0042 namespace KWin
0043 {
0044 
0045 DrmOutput::DrmOutput(const std::shared_ptr<DrmConnector> &conn)
0046     : DrmAbstractOutput(conn->gpu())
0047     , m_pipeline(conn->pipeline())
0048     , m_connector(conn)
0049 {
0050     RenderLoopPrivate::get(m_renderLoop.get())->canDoTearing = gpu()->asyncPageflipSupported();
0051     m_pipeline->setOutput(this);
0052     m_renderLoop->setRefreshRate(m_pipeline->mode()->refreshRate());
0053 
0054     Capabilities capabilities = Capability::Dpms;
0055     State initialState;
0056 
0057     if (conn->hasOverscan()) {
0058         capabilities |= Capability::Overscan;
0059         initialState.overscan = conn->overscan();
0060     }
0061     if (conn->vrrCapable()) {
0062         capabilities |= Capability::Vrr;
0063         setVrrPolicy(RenderLoop::VrrPolicy::Automatic);
0064     }
0065     if (conn->hasRgbRange()) {
0066         capabilities |= Capability::RgbRange;
0067         initialState.rgbRange = conn->rgbRange();
0068     }
0069 
0070     const Edid *edid = conn->edid();
0071 
0072     setInformation(Information{
0073         .name = conn->connectorName(),
0074         .manufacturer = edid->manufacturerString(),
0075         .model = conn->modelName(),
0076         .serialNumber = edid->serialNumber(),
0077         .eisaId = edid->eisaId(),
0078         .physicalSize = conn->physicalSize(),
0079         .edid = edid->raw(),
0080         .subPixel = conn->subpixel(),
0081         .capabilities = capabilities,
0082         .panelOrientation = DrmConnector::toKWinTransform(conn->panelOrientation()),
0083         .internal = conn->isInternal(),
0084         .nonDesktop = conn->isNonDesktop(),
0085     });
0086 
0087     initialState.modes = getModes();
0088     initialState.currentMode = m_pipeline->mode();
0089     if (!initialState.currentMode) {
0090         initialState.currentMode = initialState.modes.constFirst();
0091     }
0092 
0093     setState(initialState);
0094 
0095     m_turnOffTimer.setSingleShot(true);
0096     m_turnOffTimer.setInterval(dimAnimationTime());
0097     connect(&m_turnOffTimer, &QTimer::timeout, this, [this] {
0098         setDrmDpmsMode(DpmsMode::Off);
0099     });
0100 }
0101 
0102 DrmOutput::~DrmOutput()
0103 {
0104     m_pipeline->setOutput(nullptr);
0105 }
0106 
0107 bool DrmOutput::addLeaseObjects(QVector<uint32_t> &objectList)
0108 {
0109     if (!m_pipeline->crtc()) {
0110         qCWarning(KWIN_DRM) << "Can't lease connector: No suitable crtc available";
0111         return false;
0112     }
0113     qCDebug(KWIN_DRM) << "adding connector" << m_pipeline->connector()->id() << "to lease";
0114     objectList << m_pipeline->connector()->id();
0115     objectList << m_pipeline->crtc()->id();
0116     if (m_pipeline->crtc()->primaryPlane()) {
0117         objectList << m_pipeline->crtc()->primaryPlane()->id();
0118     }
0119     return true;
0120 }
0121 
0122 void DrmOutput::leased(DrmLease *lease)
0123 {
0124     m_lease = lease;
0125 }
0126 
0127 void DrmOutput::leaseEnded()
0128 {
0129     qCDebug(KWIN_DRM) << "ended lease for connector" << m_pipeline->connector()->id();
0130     m_lease = nullptr;
0131 }
0132 
0133 DrmLease *DrmOutput::lease() const
0134 {
0135     return m_lease;
0136 }
0137 
0138 bool DrmOutput::setCursor(CursorSource *source)
0139 {
0140     static bool valid;
0141     static const bool forceSoftwareCursor = qEnvironmentVariableIntValue("KWIN_FORCE_SW_CURSOR", &valid) == 1 && valid;
0142     // hardware cursors are broken with the NVidia proprietary driver
0143     if (forceSoftwareCursor || (!valid && m_gpu->isNVidia())) {
0144         m_setCursorSuccessful = false;
0145         return false;
0146     }
0147     const auto layer = m_pipeline->cursorLayer();
0148     if (!m_pipeline->crtc() || !layer) {
0149         return false;
0150     }
0151     m_cursor.source = source;
0152     if (!m_cursor.source || m_cursor.source->size().isEmpty()) {
0153         if (layer->isVisible()) {
0154             layer->setVisible(false);
0155             m_pipeline->setCursor();
0156         }
0157         return true;
0158     }
0159     bool rendered = false;
0160     const QMatrix4x4 monitorMatrix = logicalToNativeMatrix(rect(), scale(), transform());
0161     const QSize cursorSize = m_cursor.source->size();
0162     const QRect cursorRect = QRect(m_cursor.position, cursorSize);
0163     const QRect nativeCursorRect = monitorMatrix.mapRect(cursorRect);
0164     if (nativeCursorRect.width() <= m_gpu->cursorSize().width() && nativeCursorRect.height() <= m_gpu->cursorSize().height()) {
0165         if (auto beginInfo = layer->beginFrame()) {
0166             RenderTarget *renderTarget = &beginInfo->renderTarget;
0167             renderTarget->setDevicePixelRatio(scale());
0168 
0169             RenderLayer renderLayer(m_renderLoop.get());
0170             renderLayer.setDelegate(std::make_unique<SceneDelegate>(Compositor::self()->cursorScene()));
0171 
0172             renderLayer.delegate()->prePaint();
0173             renderLayer.delegate()->paint(renderTarget, infiniteRegion());
0174             renderLayer.delegate()->postPaint();
0175 
0176             rendered = layer->endFrame(infiniteRegion(), infiniteRegion());
0177         }
0178     }
0179     if (!rendered) {
0180         if (layer->isVisible()) {
0181             layer->setVisible(false);
0182             m_pipeline->setCursor();
0183         }
0184         m_setCursorSuccessful = false;
0185         return false;
0186     }
0187 
0188     const QSize layerSize = m_gpu->cursorSize() / scale();
0189     const QRect layerRect = monitorMatrix.mapRect(QRect(m_cursor.position, layerSize));
0190     layer->setVisible(cursorRect.intersects(rect()));
0191     if (layer->isVisible()) {
0192         m_setCursorSuccessful = m_pipeline->setCursor(logicalToNativeMatrix(QRect(QPoint(), layerRect.size()), scale(), transform()).map(m_cursor.source->hotspot()));
0193         layer->setVisible(m_setCursorSuccessful);
0194     }
0195     return m_setCursorSuccessful;
0196 }
0197 
0198 bool DrmOutput::moveCursor(const QPoint &position)
0199 {
0200     if (!m_setCursorSuccessful || !m_pipeline->crtc()) {
0201         return false;
0202     }
0203     m_cursor.position = position;
0204 
0205     const QSize cursorSize = m_cursor.source ? m_cursor.source->size() : QSize(0, 0);
0206     const QRect cursorRect = QRect(m_cursor.position, cursorSize);
0207 
0208     if (!cursorRect.intersects(rect())) {
0209         const auto layer = m_pipeline->cursorLayer();
0210         if (layer->isVisible()) {
0211             layer->setVisible(false);
0212             m_pipeline->setCursor();
0213         }
0214         return true;
0215     }
0216     const QMatrix4x4 monitorMatrix = logicalToNativeMatrix(rect(), scale(), transform());
0217     const QSize layerSize = m_gpu->cursorSize() / scale();
0218     const QRect layerRect = monitorMatrix.mapRect(QRect(m_cursor.position, layerSize));
0219     const auto layer = m_pipeline->cursorLayer();
0220     const bool wasVisible = layer->isVisible();
0221     layer->setVisible(true);
0222     layer->setPosition(layerRect.topLeft());
0223     m_moveCursorSuccessful = m_pipeline->moveCursor();
0224     layer->setVisible(m_moveCursorSuccessful);
0225     if (!m_moveCursorSuccessful || !wasVisible) {
0226         m_pipeline->setCursor();
0227     }
0228     return m_moveCursorSuccessful;
0229 }
0230 
0231 QList<std::shared_ptr<OutputMode>> DrmOutput::getModes() const
0232 {
0233     const auto drmModes = m_pipeline->connector()->modes();
0234 
0235     QList<std::shared_ptr<OutputMode>> ret;
0236     ret.reserve(drmModes.count());
0237     for (const auto &drmMode : drmModes) {
0238         ret.append(drmMode);
0239     }
0240     return ret;
0241 }
0242 
0243 void DrmOutput::setDpmsMode(DpmsMode mode)
0244 {
0245     if (mode == DpmsMode::Off) {
0246         if (!m_turnOffTimer.isActive()) {
0247             Q_EMIT aboutToTurnOff(std::chrono::milliseconds(m_turnOffTimer.interval()));
0248             m_turnOffTimer.start();
0249         }
0250         if (isEnabled()) {
0251             m_gpu->platform()->createDpmsFilter();
0252         }
0253     } else {
0254         m_gpu->platform()->checkOutputsAreOn();
0255         if (m_turnOffTimer.isActive() || (mode != dpmsMode() && setDrmDpmsMode(mode))) {
0256             Q_EMIT wakeUp();
0257         }
0258         m_turnOffTimer.stop();
0259     }
0260 }
0261 
0262 bool DrmOutput::setDrmDpmsMode(DpmsMode mode)
0263 {
0264     if (!isEnabled()) {
0265         return false;
0266     }
0267     bool active = mode == DpmsMode::On;
0268     bool isActive = dpmsMode() == DpmsMode::On;
0269     if (active == isActive) {
0270         updateDpmsMode(mode);
0271         return true;
0272     }
0273     m_pipeline->setActive(active);
0274     if (DrmPipeline::commitPipelines({m_pipeline}, active ? DrmPipeline::CommitMode::TestAllowModeset : DrmPipeline::CommitMode::CommitModeset) == DrmPipeline::Error::None) {
0275         m_pipeline->applyPendingChanges();
0276         updateDpmsMode(mode);
0277         if (active) {
0278             m_gpu->platform()->checkOutputsAreOn();
0279             m_renderLoop->uninhibit();
0280             m_renderLoop->scheduleRepaint();
0281         } else {
0282             m_renderLoop->inhibit();
0283             m_gpu->platform()->createDpmsFilter();
0284         }
0285         return true;
0286     } else {
0287         qCWarning(KWIN_DRM) << "Setting dpms mode failed!";
0288         m_pipeline->revertPendingChanges();
0289         if (isEnabled() && isActive && !active) {
0290             m_gpu->platform()->checkOutputsAreOn();
0291         }
0292         return false;
0293     }
0294 }
0295 
0296 DrmPlane::Transformations outputToPlaneTransform(DrmOutput::Transform transform)
0297 {
0298     using OutTrans = DrmOutput::Transform;
0299     using PlaneTrans = DrmPlane::Transformation;
0300 
0301     // TODO: Do we want to support reflections (flips)?
0302 
0303     switch (transform) {
0304     case OutTrans::Normal:
0305     case OutTrans::Flipped:
0306         return PlaneTrans::Rotate0;
0307     case OutTrans::Rotated90:
0308     case OutTrans::Flipped90:
0309         return PlaneTrans::Rotate90;
0310     case OutTrans::Rotated180:
0311     case OutTrans::Flipped180:
0312         return PlaneTrans::Rotate180;
0313     case OutTrans::Rotated270:
0314     case OutTrans::Flipped270:
0315         return PlaneTrans::Rotate270;
0316     default:
0317         Q_UNREACHABLE();
0318     }
0319 }
0320 
0321 void DrmOutput::updateModes()
0322 {
0323     State next = m_state;
0324     next.modes = getModes();
0325 
0326     if (m_pipeline->crtc()) {
0327         const auto currentMode = m_pipeline->connector()->findMode(m_pipeline->crtc()->queryCurrentMode());
0328         if (currentMode != m_pipeline->mode()) {
0329             // DrmConnector::findCurrentMode might fail
0330             m_pipeline->setMode(currentMode ? currentMode : m_pipeline->connector()->modes().constFirst());
0331             if (m_gpu->testPendingConfiguration() == DrmPipeline::Error::None) {
0332                 m_pipeline->applyPendingChanges();
0333                 m_renderLoop->setRefreshRate(m_pipeline->mode()->refreshRate());
0334             } else {
0335                 qCWarning(KWIN_DRM) << "Setting changed mode failed!";
0336                 m_pipeline->revertPendingChanges();
0337             }
0338         }
0339     }
0340 
0341     next.currentMode = m_pipeline->mode();
0342     if (!next.currentMode) {
0343         next.currentMode = next.modes.constFirst();
0344     }
0345 
0346     setState(next);
0347 }
0348 
0349 void DrmOutput::updateDpmsMode(DpmsMode dpmsMode)
0350 {
0351     State next = m_state;
0352     next.dpmsMode = dpmsMode;
0353     setState(next);
0354 }
0355 
0356 bool DrmOutput::present()
0357 {
0358     RenderLoopPrivate *renderLoopPrivate = RenderLoopPrivate::get(m_renderLoop.get());
0359     const auto type = DrmConnector::kwinToDrmContentType(contentType());
0360     if (m_pipeline->syncMode() != renderLoopPrivate->presentMode || type != m_pipeline->contentType()) {
0361         m_pipeline->setSyncMode(renderLoopPrivate->presentMode);
0362         m_pipeline->setContentType(type);
0363         if (DrmPipeline::commitPipelines({m_pipeline}, DrmPipeline::CommitMode::Test) == DrmPipeline::Error::None) {
0364             m_pipeline->applyPendingChanges();
0365         } else {
0366             m_pipeline->revertPendingChanges();
0367         }
0368     }
0369     const bool needsModeset = gpu()->needsModeset();
0370     bool success;
0371     if (needsModeset) {
0372         success = m_pipeline->maybeModeset();
0373     } else {
0374         DrmPipeline::Error err = m_pipeline->present();
0375         success = err == DrmPipeline::Error::None;
0376         if (err == DrmPipeline::Error::InvalidArguments) {
0377             QTimer::singleShot(0, m_gpu->platform(), &DrmBackend::updateOutputs);
0378         }
0379     }
0380     if (success) {
0381         Q_EMIT outputChange(m_pipeline->primaryLayer()->currentDamage());
0382         return true;
0383     } else if (!needsModeset) {
0384         qCWarning(KWIN_DRM) << "Presentation failed!" << strerror(errno);
0385         frameFailed();
0386     }
0387     return false;
0388 }
0389 
0390 DrmConnector *DrmOutput::connector() const
0391 {
0392     return m_connector.get();
0393 }
0394 
0395 DrmPipeline *DrmOutput::pipeline() const
0396 {
0397     return m_pipeline;
0398 }
0399 
0400 bool DrmOutput::queueChanges(const OutputConfiguration &config)
0401 {
0402     static bool valid;
0403     static int envOnlySoftwareRotations = qEnvironmentVariableIntValue("KWIN_DRM_SW_ROTATIONS_ONLY", &valid) == 1 || !valid;
0404 
0405     const auto props = config.constChangeSet(this);
0406     const auto mode = props->mode.lock();
0407     if (!mode) {
0408         return false;
0409     }
0410     m_pipeline->setMode(std::static_pointer_cast<DrmConnectorMode>(mode));
0411     m_pipeline->setOverscan(props->overscan);
0412     m_pipeline->setRgbRange(props->rgbRange);
0413     m_pipeline->setRenderOrientation(outputToPlaneTransform(props->transform));
0414     if (!envOnlySoftwareRotations && m_gpu->atomicModeSetting()) {
0415         m_pipeline->setBufferOrientation(m_pipeline->renderOrientation());
0416     }
0417     m_pipeline->setEnable(props->enabled);
0418     return true;
0419 }
0420 
0421 void DrmOutput::applyQueuedChanges(const OutputConfiguration &config)
0422 {
0423     if (!m_connector->isConnected()) {
0424         return;
0425     }
0426     Q_EMIT aboutToChange();
0427     m_pipeline->applyPendingChanges();
0428 
0429     auto props = config.constChangeSet(this);
0430 
0431     State next = m_state;
0432     next.enabled = props->enabled && m_pipeline->crtc();
0433     next.position = props->pos;
0434     next.scale = props->scale;
0435     next.transform = props->transform;
0436     next.currentMode = m_pipeline->mode();
0437     next.overscan = m_pipeline->overscan();
0438     next.rgbRange = m_pipeline->rgbRange();
0439 
0440     setState(next);
0441     setVrrPolicy(props->vrrPolicy);
0442 
0443     if (!isEnabled() && m_pipeline->needsModeset()) {
0444         m_gpu->maybeModeset();
0445     }
0446 
0447     m_renderLoop->setRefreshRate(refreshRate());
0448     m_renderLoop->scheduleRepaint();
0449 
0450     Q_EMIT changed();
0451 
0452     if (isEnabled() && dpmsMode() == DpmsMode::On) {
0453         m_gpu->platform()->turnOutputsOn();
0454     }
0455 }
0456 
0457 void DrmOutput::revertQueuedChanges()
0458 {
0459     m_pipeline->revertPendingChanges();
0460 }
0461 
0462 DrmOutputLayer *DrmOutput::primaryLayer() const
0463 {
0464     return m_pipeline->primaryLayer();
0465 }
0466 
0467 bool DrmOutput::setGammaRamp(const std::shared_ptr<ColorTransformation> &transformation)
0468 {
0469     if (!m_pipeline->activePending()) {
0470         return false;
0471     }
0472     m_pipeline->setGammaRamp(transformation);
0473     m_pipeline->setCTM(QMatrix3x3());
0474     if (DrmPipeline::commitPipelines({m_pipeline}, DrmPipeline::CommitMode::Test) == DrmPipeline::Error::None) {
0475         m_pipeline->applyPendingChanges();
0476         m_renderLoop->scheduleRepaint();
0477         return true;
0478     } else {
0479         m_pipeline->revertPendingChanges();
0480         return false;
0481     }
0482 }
0483 
0484 bool DrmOutput::setCTM(const QMatrix3x3 &ctm)
0485 {
0486     if (!m_pipeline->activePending()) {
0487         return false;
0488     }
0489     m_pipeline->setCTM(ctm);
0490     if (DrmPipeline::commitPipelines({m_pipeline}, DrmPipeline::CommitMode::Test) == DrmPipeline::Error::None) {
0491         m_pipeline->applyPendingChanges();
0492         m_renderLoop->scheduleRepaint();
0493         return true;
0494     } else {
0495         m_pipeline->revertPendingChanges();
0496         return false;
0497     }
0498 }
0499 }