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"