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 }