File indexing completed on 2024-11-10 04:56:29
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_connector.h" 0012 #include "drm_crtc.h" 0013 #include "drm_gpu.h" 0014 #include "drm_pipeline.h" 0015 0016 #include "core/colortransformation.h" 0017 #include "core/iccprofile.h" 0018 #include "core/outputconfiguration.h" 0019 #include "core/renderbackend.h" 0020 #include "core/renderloop.h" 0021 #include "core/renderloop_p.h" 0022 #include "drm_layer.h" 0023 #include "drm_logging.h" 0024 // Qt 0025 #include <QCryptographicHash> 0026 #include <QMatrix4x4> 0027 #include <QPainter> 0028 // c++ 0029 #include <cerrno> 0030 // drm 0031 #include <drm_fourcc.h> 0032 #include <libdrm/drm_mode.h> 0033 #include <xf86drm.h> 0034 0035 namespace KWin 0036 { 0037 0038 DrmOutput::DrmOutput(const std::shared_ptr<DrmConnector> &conn) 0039 : DrmAbstractOutput(conn->gpu()) 0040 , m_pipeline(conn->pipeline()) 0041 , m_connector(conn) 0042 { 0043 m_pipeline->setOutput(this); 0044 m_renderLoop->setRefreshRate(m_pipeline->mode()->refreshRate()); 0045 0046 Capabilities capabilities = Capability::Dpms | Capability::IccProfile; 0047 State initialState; 0048 0049 if (conn->overscan.isValid() || conn->underscan.isValid()) { 0050 capabilities |= Capability::Overscan; 0051 initialState.overscan = conn->overscan.isValid() ? conn->overscan.value() : conn->underscanVBorder.value(); 0052 } 0053 if (conn->vrrCapable.isValid() && conn->vrrCapable.value()) { 0054 capabilities |= Capability::Vrr; 0055 } 0056 if (gpu()->asyncPageflipSupported()) { 0057 capabilities |= Capability::Tearing; 0058 } 0059 if (conn->broadcastRGB.isValid()) { 0060 capabilities |= Capability::RgbRange; 0061 initialState.rgbRange = DrmConnector::broadcastRgbToRgbRange(conn->broadcastRGB.enumValue()); 0062 } 0063 if (m_connector->hdrMetadata.isValid() && m_connector->edid()->supportsPQ()) { 0064 capabilities |= Capability::HighDynamicRange; 0065 } 0066 if (m_connector->colorspace.isValid() && m_connector->colorspace.hasEnum(DrmConnector::Colorspace::BT2020_RGB) && m_connector->edid()->supportsBT2020()) { 0067 capabilities |= Capability::WideColorGamut; 0068 } 0069 if (conn->isInternal()) { 0070 // TODO only set this if an orientation sensor is available? 0071 capabilities |= Capability::AutoRotation; 0072 } 0073 0074 const Edid *edid = conn->edid(); 0075 setInformation(Information{ 0076 .name = conn->connectorName(), 0077 .manufacturer = edid->manufacturerString(), 0078 .model = conn->modelName(), 0079 .serialNumber = edid->serialNumber(), 0080 .eisaId = edid->eisaId(), 0081 .physicalSize = conn->physicalSize(), 0082 .edid = *edid, 0083 .subPixel = conn->subpixel(), 0084 .capabilities = capabilities, 0085 .panelOrientation = conn->panelOrientation.isValid() ? DrmConnector::toKWinTransform(conn->panelOrientation.enumValue()) : OutputTransform::Normal, 0086 .internal = conn->isInternal(), 0087 .nonDesktop = conn->isNonDesktop(), 0088 .mstPath = conn->mstPath(), 0089 .maxPeakBrightness = edid->desiredMaxLuminance(), 0090 .maxAverageBrightness = edid->desiredMaxFrameAverageLuminance(), 0091 .minBrightness = edid->desiredMinLuminance(), 0092 }); 0093 0094 initialState.modes = getModes(); 0095 initialState.currentMode = m_pipeline->mode(); 0096 if (!initialState.currentMode) { 0097 initialState.currentMode = initialState.modes.constFirst(); 0098 } 0099 0100 setState(initialState); 0101 0102 m_turnOffTimer.setSingleShot(true); 0103 m_turnOffTimer.setInterval(dimAnimationTime()); 0104 connect(&m_turnOffTimer, &QTimer::timeout, this, [this] { 0105 setDrmDpmsMode(DpmsMode::Off); 0106 }); 0107 } 0108 0109 DrmOutput::~DrmOutput() 0110 { 0111 m_pipeline->setOutput(nullptr); 0112 } 0113 0114 bool DrmOutput::addLeaseObjects(QList<uint32_t> &objectList) 0115 { 0116 if (!m_pipeline->crtc()) { 0117 qCWarning(KWIN_DRM) << "Can't lease connector: No suitable crtc available"; 0118 return false; 0119 } 0120 qCDebug(KWIN_DRM) << "adding connector" << m_pipeline->connector()->id() << "to lease"; 0121 objectList << m_pipeline->connector()->id(); 0122 objectList << m_pipeline->crtc()->id(); 0123 if (m_pipeline->crtc()->primaryPlane()) { 0124 objectList << m_pipeline->crtc()->primaryPlane()->id(); 0125 } 0126 return true; 0127 } 0128 0129 void DrmOutput::leased(DrmLease *lease) 0130 { 0131 m_lease = lease; 0132 } 0133 0134 void DrmOutput::leaseEnded() 0135 { 0136 qCDebug(KWIN_DRM) << "ended lease for connector" << m_pipeline->connector()->id(); 0137 m_lease = nullptr; 0138 } 0139 0140 DrmLease *DrmOutput::lease() const 0141 { 0142 return m_lease; 0143 } 0144 0145 bool DrmOutput::updateCursorLayer() 0146 { 0147 return m_pipeline->updateCursor(); 0148 } 0149 0150 QList<std::shared_ptr<OutputMode>> DrmOutput::getModes() const 0151 { 0152 const auto drmModes = m_pipeline->connector()->modes(); 0153 0154 QList<std::shared_ptr<OutputMode>> ret; 0155 ret.reserve(drmModes.count()); 0156 for (const auto &drmMode : drmModes) { 0157 ret.append(drmMode); 0158 } 0159 return ret; 0160 } 0161 0162 void DrmOutput::setDpmsMode(DpmsMode mode) 0163 { 0164 if (mode == DpmsMode::Off) { 0165 if (!m_turnOffTimer.isActive()) { 0166 Q_EMIT aboutToTurnOff(std::chrono::milliseconds(m_turnOffTimer.interval())); 0167 m_turnOffTimer.start(); 0168 } 0169 } else { 0170 if (m_turnOffTimer.isActive() || (mode != dpmsMode() && setDrmDpmsMode(mode))) { 0171 Q_EMIT wakeUp(); 0172 } 0173 m_turnOffTimer.stop(); 0174 } 0175 } 0176 0177 bool DrmOutput::setDrmDpmsMode(DpmsMode mode) 0178 { 0179 if (!isEnabled()) { 0180 return false; 0181 } 0182 bool active = mode == DpmsMode::On; 0183 bool isActive = dpmsMode() == DpmsMode::On; 0184 if (active == isActive) { 0185 updateDpmsMode(mode); 0186 return true; 0187 } 0188 if (!active) { 0189 gpu()->waitIdle(); 0190 } 0191 m_pipeline->setActive(active); 0192 if (DrmPipeline::commitPipelines({m_pipeline}, active ? DrmPipeline::CommitMode::TestAllowModeset : DrmPipeline::CommitMode::CommitModeset) == DrmPipeline::Error::None) { 0193 m_pipeline->applyPendingChanges(); 0194 updateDpmsMode(mode); 0195 if (active) { 0196 m_renderLoop->uninhibit(); 0197 m_renderLoop->scheduleRepaint(); 0198 doSetChannelFactors(m_channelFactors); 0199 } else { 0200 m_renderLoop->inhibit(); 0201 } 0202 return true; 0203 } else { 0204 qCWarning(KWIN_DRM) << "Setting dpms mode failed!"; 0205 m_pipeline->revertPendingChanges(); 0206 return false; 0207 } 0208 } 0209 0210 DrmPlane::Transformations outputToPlaneTransform(OutputTransform transform) 0211 { 0212 using PlaneTrans = DrmPlane::Transformation; 0213 0214 switch (transform.kind()) { 0215 case OutputTransform::Normal: 0216 return PlaneTrans::Rotate0; 0217 case OutputTransform::FlipX: 0218 return PlaneTrans::ReflectX | PlaneTrans::Rotate0; 0219 case OutputTransform::Rotate90: 0220 return PlaneTrans::Rotate90; 0221 case OutputTransform::FlipX90: 0222 return PlaneTrans::ReflectX | PlaneTrans::Rotate90; 0223 case OutputTransform::Rotate180: 0224 return PlaneTrans::Rotate180; 0225 case OutputTransform::FlipX180: 0226 return PlaneTrans::ReflectX | PlaneTrans::Rotate180; 0227 case OutputTransform::Rotate270: 0228 return PlaneTrans::Rotate270; 0229 case OutputTransform::FlipX270: 0230 return PlaneTrans::ReflectX | PlaneTrans::Rotate270; 0231 default: 0232 Q_UNREACHABLE(); 0233 } 0234 } 0235 0236 void DrmOutput::updateModes() 0237 { 0238 State next = m_state; 0239 next.modes = getModes(); 0240 0241 if (m_pipeline->crtc()) { 0242 const auto currentMode = m_pipeline->connector()->findMode(m_pipeline->crtc()->queryCurrentMode()); 0243 if (currentMode != m_pipeline->mode()) { 0244 // DrmConnector::findCurrentMode might fail 0245 m_pipeline->setMode(currentMode ? currentMode : m_pipeline->connector()->modes().constFirst()); 0246 if (m_gpu->testPendingConfiguration() == DrmPipeline::Error::None) { 0247 m_pipeline->applyPendingChanges(); 0248 m_renderLoop->setRefreshRate(m_pipeline->mode()->refreshRate()); 0249 } else { 0250 qCWarning(KWIN_DRM) << "Setting changed mode failed!"; 0251 m_pipeline->revertPendingChanges(); 0252 } 0253 } 0254 } 0255 0256 next.currentMode = m_pipeline->mode(); 0257 if (!next.currentMode) { 0258 next.currentMode = next.modes.constFirst(); 0259 } 0260 0261 setState(next); 0262 } 0263 0264 void DrmOutput::updateDpmsMode(DpmsMode dpmsMode) 0265 { 0266 State next = m_state; 0267 next.dpmsMode = dpmsMode; 0268 setState(next); 0269 } 0270 0271 bool DrmOutput::present(const std::shared_ptr<OutputFrame> &frame) 0272 { 0273 m_frame = frame; 0274 const bool needsModeset = gpu()->needsModeset(); 0275 bool success; 0276 if (needsModeset) { 0277 m_pipeline->setPresentationMode(PresentationMode::VSync); 0278 m_pipeline->setContentType(DrmConnector::DrmContentType::Graphics); 0279 success = m_pipeline->maybeModeset(); 0280 } else { 0281 m_pipeline->setPresentationMode(frame->presentationMode()); 0282 DrmPipeline::Error err = m_pipeline->present(); 0283 if (err != DrmPipeline::Error::None && frame->presentationMode() != PresentationMode::VSync) { 0284 // retry with a more basic presentation mode 0285 m_pipeline->setPresentationMode(PresentationMode::VSync); 0286 err = m_pipeline->present(); 0287 } 0288 success = err == DrmPipeline::Error::None; 0289 if (err == DrmPipeline::Error::InvalidArguments) { 0290 QTimer::singleShot(0, m_gpu->platform(), &DrmBackend::updateOutputs); 0291 } 0292 } 0293 m_renderLoop->setPresentationMode(m_pipeline->presentationMode()); 0294 if (success) { 0295 Q_EMIT outputChange(m_pipeline->primaryLayer()->currentDamage()); 0296 return true; 0297 } else if (!needsModeset) { 0298 qCWarning(KWIN_DRM) << "Presentation failed!" << strerror(errno); 0299 m_frame->failed(); 0300 } 0301 return false; 0302 } 0303 0304 DrmConnector *DrmOutput::connector() const 0305 { 0306 return m_connector.get(); 0307 } 0308 0309 DrmPipeline *DrmOutput::pipeline() const 0310 { 0311 return m_pipeline; 0312 } 0313 0314 bool DrmOutput::queueChanges(const std::shared_ptr<OutputChangeSet> &props) 0315 { 0316 const auto mode = props->mode.value_or(currentMode()).lock(); 0317 if (!mode) { 0318 return false; 0319 } 0320 const bool bt2020 = props->wideColorGamut.value_or(m_state.wideColorGamut); 0321 const bool hdr = props->highDynamicRange.value_or(m_state.highDynamicRange); 0322 m_pipeline->setMode(std::static_pointer_cast<DrmConnectorMode>(mode)); 0323 m_pipeline->setOverscan(props->overscan.value_or(m_pipeline->overscan())); 0324 m_pipeline->setRgbRange(props->rgbRange.value_or(m_pipeline->rgbRange())); 0325 m_pipeline->setRenderOrientation(outputToPlaneTransform(props->transform.value_or(transform()))); 0326 m_pipeline->setEnable(props->enabled.value_or(m_pipeline->enabled())); 0327 m_pipeline->setColorDescription(createColorDescription(props)); 0328 if (bt2020 || hdr) { 0329 // ICC profiles don't support HDR (yet) 0330 m_pipeline->setIccProfile(nullptr); 0331 } else { 0332 m_pipeline->setIccProfile(props->iccProfile.value_or(m_state.iccProfile)); 0333 } 0334 if (bt2020 || hdr || m_pipeline->iccProfile()) { 0335 // remove unused gamma ramp and ctm, if present 0336 m_pipeline->setGammaRamp(nullptr); 0337 m_pipeline->setCTM(QMatrix3x3{}); 0338 } 0339 return true; 0340 } 0341 0342 ColorDescription DrmOutput::createColorDescription(const std::shared_ptr<OutputChangeSet> &props) const 0343 { 0344 if (props->highDynamicRange.value_or(m_state.highDynamicRange) && m_connector->edid()) { 0345 const auto colorimetry = props->wideColorGamut.value_or(m_state.wideColorGamut) ? NamedColorimetry::BT2020 : NamedColorimetry::BT709; 0346 const auto nativeColorimetry = m_information.edid.colorimetry().value_or(Colorimetry::fromName(NamedColorimetry::BT709)); 0347 const auto sdrBrightness = props->sdrBrightness.value_or(m_state.sdrBrightness); 0348 return ColorDescription(colorimetry, NamedTransferFunction::PerceptualQuantizer, sdrBrightness, 0349 props->minBrightnessOverride.value_or(m_state.minBrightnessOverride).value_or(m_connector->edid()->desiredMinLuminance()), 0350 props->maxAverageBrightnessOverride.value_or(m_state.maxAverageBrightnessOverride).value_or(m_connector->edid()->desiredMaxFrameAverageLuminance().value_or(sdrBrightness)), 0351 props->maxPeakBrightnessOverride.value_or(m_state.maxPeakBrightnessOverride).value_or(m_connector->edid()->desiredMaxLuminance().value_or(1000)), 0352 Colorimetry::fromName(NamedColorimetry::BT709).interpolateGamutTo(nativeColorimetry, props->sdrGamutWideness.value_or(m_state.sdrGamutWideness))); 0353 } else if (const auto profile = props->iccProfile.value_or(m_state.iccProfile)) { 0354 return ColorDescription(profile->colorimetry(), NamedTransferFunction::gamma22, 200, 0, 200, 200); 0355 } else { 0356 return ColorDescription::sRGB; 0357 } 0358 } 0359 0360 void DrmOutput::applyQueuedChanges(const std::shared_ptr<OutputChangeSet> &props) 0361 { 0362 if (!m_connector->isConnected()) { 0363 return; 0364 } 0365 Q_EMIT aboutToChange(props.get()); 0366 m_pipeline->applyPendingChanges(); 0367 0368 State next = m_state; 0369 next.enabled = props->enabled.value_or(m_state.enabled) && m_pipeline->crtc(); 0370 next.position = props->pos.value_or(m_state.position); 0371 next.scale = props->scale.value_or(m_state.scale); 0372 next.transform = props->transform.value_or(m_state.transform); 0373 next.manualTransform = props->manualTransform.value_or(m_state.manualTransform); 0374 next.currentMode = m_pipeline->mode(); 0375 next.overscan = m_pipeline->overscan(); 0376 next.rgbRange = m_pipeline->rgbRange(); 0377 next.highDynamicRange = props->highDynamicRange.value_or(m_state.highDynamicRange); 0378 next.sdrBrightness = props->sdrBrightness.value_or(m_state.sdrBrightness); 0379 next.wideColorGamut = props->wideColorGamut.value_or(m_state.wideColorGamut); 0380 next.autoRotatePolicy = props->autoRotationPolicy.value_or(m_state.autoRotatePolicy); 0381 next.maxPeakBrightnessOverride = props->maxPeakBrightnessOverride.value_or(m_state.maxPeakBrightnessOverride); 0382 next.maxAverageBrightnessOverride = props->maxAverageBrightnessOverride.value_or(m_state.maxAverageBrightnessOverride); 0383 next.minBrightnessOverride = props->minBrightnessOverride.value_or(m_state.minBrightnessOverride); 0384 next.sdrGamutWideness = props->sdrGamutWideness.value_or(m_state.sdrGamutWideness); 0385 next.iccProfilePath = props->iccProfilePath.value_or(m_state.iccProfilePath); 0386 next.iccProfile = props->iccProfile.value_or(m_state.iccProfile); 0387 next.colorDescription = m_pipeline->colorDescription(); 0388 next.vrrPolicy = props->vrrPolicy.value_or(m_state.vrrPolicy); 0389 setState(next); 0390 0391 if (!isEnabled() && m_pipeline->needsModeset()) { 0392 m_gpu->maybeModeset(); 0393 } 0394 0395 m_renderLoop->setRefreshRate(refreshRate()); 0396 m_renderLoop->scheduleRepaint(); 0397 0398 if (!next.wideColorGamut && !next.highDynamicRange && !m_pipeline->iccProfile()) { 0399 // re-set the CTM and/or gamma lut 0400 doSetChannelFactors(m_channelFactors); 0401 } 0402 0403 Q_EMIT changed(); 0404 } 0405 0406 void DrmOutput::revertQueuedChanges() 0407 { 0408 m_pipeline->revertPendingChanges(); 0409 } 0410 0411 DrmOutputLayer *DrmOutput::primaryLayer() const 0412 { 0413 return m_pipeline->primaryLayer(); 0414 } 0415 0416 DrmOutputLayer *DrmOutput::cursorLayer() const 0417 { 0418 return m_pipeline->cursorLayer(); 0419 } 0420 0421 bool DrmOutput::setChannelFactors(const QVector3D &rgb) 0422 { 0423 return m_channelFactors == rgb || doSetChannelFactors(rgb); 0424 } 0425 0426 bool DrmOutput::doSetChannelFactors(const QVector3D &rgb) 0427 { 0428 m_renderLoop->scheduleRepaint(); 0429 m_channelFactors = rgb; 0430 if (m_state.wideColorGamut || m_state.highDynamicRange || m_state.iccProfile) { 0431 // the shader "fallback" is always active 0432 return true; 0433 } 0434 if (!m_pipeline->activePending()) { 0435 return false; 0436 } 0437 const auto inGamma22 = ColorDescription::nitsToEncoded(rgb, NamedTransferFunction::gamma22, 1); 0438 if (m_pipeline->hasCTM()) { 0439 QMatrix3x3 ctm; 0440 ctm(0, 0) = inGamma22.x(); 0441 ctm(1, 1) = inGamma22.y(); 0442 ctm(2, 2) = inGamma22.z(); 0443 m_pipeline->setCTM(ctm); 0444 m_pipeline->setGammaRamp(nullptr); 0445 if (DrmPipeline::commitPipelines({m_pipeline}, DrmPipeline::CommitMode::Test) == DrmPipeline::Error::None) { 0446 m_pipeline->applyPendingChanges(); 0447 m_channelFactorsNeedShaderFallback = false; 0448 return true; 0449 } else { 0450 m_pipeline->setCTM(QMatrix3x3()); 0451 m_pipeline->applyPendingChanges(); 0452 } 0453 } 0454 if (m_pipeline->hasGammaRamp()) { 0455 auto lut = ColorTransformation::createScalingTransform(inGamma22); 0456 if (lut) { 0457 m_pipeline->setGammaRamp(std::move(lut)); 0458 if (DrmPipeline::commitPipelines({m_pipeline}, DrmPipeline::CommitMode::Test) == DrmPipeline::Error::None) { 0459 m_pipeline->applyPendingChanges(); 0460 m_channelFactorsNeedShaderFallback = false; 0461 return true; 0462 } else { 0463 m_pipeline->setGammaRamp(nullptr); 0464 m_pipeline->applyPendingChanges(); 0465 } 0466 } 0467 } 0468 m_channelFactorsNeedShaderFallback = m_channelFactors != QVector3D{1, 1, 1}; 0469 return true; 0470 } 0471 0472 QVector3D DrmOutput::channelFactors() const 0473 { 0474 return m_channelFactors; 0475 } 0476 0477 bool DrmOutput::needsColormanagement() const 0478 { 0479 static bool forceColorManagement = qEnvironmentVariableIntValue("KWIN_DRM_FORCE_COLOR_MANAGEMENT") != 0; 0480 return forceColorManagement || m_state.wideColorGamut || m_state.highDynamicRange || m_state.iccProfile || m_channelFactorsNeedShaderFallback; 0481 } 0482 } 0483 0484 #include "moc_drm_output.cpp"