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: 2021 Xaver Hugl <xaver.hugl@gmail.com>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "drm_pipeline.h"
0011 
0012 #include <errno.h>
0013 
0014 #include "core/iccprofile.h"
0015 #include "core/session.h"
0016 #include "drm_backend.h"
0017 #include "drm_buffer.h"
0018 #include "drm_commit.h"
0019 #include "drm_commit_thread.h"
0020 #include "drm_connector.h"
0021 #include "drm_crtc.h"
0022 #include "drm_egl_backend.h"
0023 #include "drm_gpu.h"
0024 #include "drm_layer.h"
0025 #include "drm_logging.h"
0026 #include "drm_output.h"
0027 #include "drm_plane.h"
0028 
0029 #include <drm_fourcc.h>
0030 #include <gbm.h>
0031 
0032 using namespace std::literals;
0033 
0034 namespace KWin
0035 {
0036 
0037 static const QList<uint64_t> implicitModifier = {DRM_FORMAT_MOD_INVALID};
0038 static const QMap<uint32_t, QList<uint64_t>> legacyFormats = {{DRM_FORMAT_XRGB8888, implicitModifier}};
0039 static const QMap<uint32_t, QList<uint64_t>> legacyCursorFormats = {{DRM_FORMAT_ARGB8888, implicitModifier}};
0040 
0041 DrmPipeline::DrmPipeline(DrmConnector *conn)
0042     : m_connector(conn)
0043     , m_commitThread(std::make_unique<DrmCommitThread>(conn->gpu(), conn->connectorName()))
0044 {
0045     QObject::connect(m_commitThread.get(), &DrmCommitThread::commitFailed, [this]() {
0046         if (m_output) {
0047             m_output->frameFailed();
0048         }
0049     });
0050 }
0051 
0052 DrmPipeline::~DrmPipeline()
0053 {
0054     if (pageflipsPending()) {
0055         gpu()->waitIdle();
0056     }
0057 }
0058 
0059 bool DrmPipeline::testScanout()
0060 {
0061     if (gpu()->needsModeset()) {
0062         return false;
0063     }
0064     if (gpu()->atomicModeSetting()) {
0065         return commitPipelines({this}, CommitMode::Test) == Error::None;
0066     } else {
0067         if (m_primaryLayer->currentBuffer()->buffer()->size() != m_pending.mode->size()) {
0068             // scaling isn't supported with the legacy API
0069             return false;
0070         }
0071         // no other way to test than to do it.
0072         // As we only have a maximum of one test per scanout cycle, this is fine
0073         return presentLegacy() == Error::None;
0074     }
0075 }
0076 
0077 DrmPipeline::Error DrmPipeline::present()
0078 {
0079     Q_ASSERT(m_pending.crtc);
0080     if (gpu()->atomicModeSetting()) {
0081         // test the full state, to take pending commits into account
0082         auto fullState = std::make_unique<DrmAtomicCommit>(QList<DrmPipeline *>{this});
0083         if (Error err = prepareAtomicCommit(fullState.get(), CommitMode::Test); err != Error::None) {
0084             return err;
0085         }
0086         if (!fullState->test()) {
0087             return errnoToError();
0088         }
0089         // only give the actual state update to the commit thread, so that it can potentially reorder the commits
0090         auto primaryPlaneUpdate = std::make_unique<DrmAtomicCommit>(QList<DrmPipeline *>{this});
0091         if (Error err = prepareAtomicPresentation(primaryPlaneUpdate.get()); err != Error::None) {
0092             return err;
0093         }
0094         if (m_pending.needsModesetProperties && !prepareAtomicModeset(primaryPlaneUpdate.get())) {
0095             return Error::InvalidArguments;
0096         }
0097         m_next.needsModesetProperties = m_pending.needsModesetProperties = false;
0098         m_commitThread->addCommit(std::move(primaryPlaneUpdate));
0099         return Error::None;
0100     } else {
0101         if (m_primaryLayer->hasDirectScanoutBuffer()) {
0102             // already presented
0103             return Error::None;
0104         }
0105         return presentLegacy();
0106     }
0107 }
0108 
0109 bool DrmPipeline::maybeModeset()
0110 {
0111     m_modesetPresentPending = true;
0112     return gpu()->maybeModeset();
0113 }
0114 
0115 DrmPipeline::Error DrmPipeline::commitPipelines(const QList<DrmPipeline *> &pipelines, CommitMode mode, const QList<DrmObject *> &unusedObjects)
0116 {
0117     Q_ASSERT(!pipelines.isEmpty());
0118     if (pipelines[0]->gpu()->atomicModeSetting()) {
0119         return commitPipelinesAtomic(pipelines, mode, unusedObjects);
0120     } else {
0121         return commitPipelinesLegacy(pipelines, mode);
0122     }
0123 }
0124 
0125 DrmPipeline::Error DrmPipeline::commitPipelinesAtomic(const QList<DrmPipeline *> &pipelines, CommitMode mode, const QList<DrmObject *> &unusedObjects)
0126 {
0127     auto commit = std::make_unique<DrmAtomicCommit>(pipelines);
0128     if (mode == CommitMode::Test) {
0129         // if there's a modeset pending, the tests on top of that state
0130         // also have to allow modesets or they'll always fail
0131         const bool wantsModeset = std::any_of(pipelines.begin(), pipelines.end(), [](DrmPipeline *pipeline) {
0132             return pipeline->needsModeset();
0133         });
0134         if (wantsModeset) {
0135             mode = CommitMode::TestAllowModeset;
0136         }
0137     }
0138     for (const auto &pipeline : pipelines) {
0139         if (Error err = pipeline->prepareAtomicCommit(commit.get(), mode); err != Error::None) {
0140             return err;
0141         }
0142     }
0143     for (const auto &unused : unusedObjects) {
0144         unused->disable(commit.get());
0145     }
0146     switch (mode) {
0147     case CommitMode::TestAllowModeset: {
0148         if (!commit->testAllowModeset()) {
0149             qCDebug(KWIN_DRM) << "Atomic modeset test failed!" << strerror(errno);
0150             return errnoToError();
0151         }
0152         const bool withoutModeset = std::all_of(pipelines.begin(), pipelines.end(), [](DrmPipeline *pipeline) {
0153             auto commit = std::make_unique<DrmAtomicCommit>(QVector<DrmPipeline *>{pipeline});
0154             return pipeline->prepareAtomicCommit(commit.get(), CommitMode::TestAllowModeset) == Error::None && commit->test();
0155         });
0156         for (const auto &pipeline : pipelines) {
0157             pipeline->m_pending.needsModeset = !withoutModeset;
0158             pipeline->m_pending.needsModesetProperties = true;
0159         }
0160         return Error::None;
0161     }
0162     case CommitMode::CommitModeset: {
0163         // The kernel fails commits with DRM_MODE_PAGE_FLIP_EVENT when a crtc is disabled in the commit
0164         // and already was disabled before, to work around some quirks in old userspace.
0165         // Instead of using DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK, do the modeset in a blocking
0166         // fashion without page flip events and trigger the pageflip notification directly
0167         if (!commit->commitModeset()) {
0168             qCCritical(KWIN_DRM) << "Atomic modeset commit failed!" << strerror(errno);
0169             return errnoToError();
0170         }
0171         for (const auto pipeline : pipelines) {
0172             pipeline->m_next.needsModeset = pipeline->m_pending.needsModeset = false;
0173         }
0174         commit->pageFlipped(std::chrono::steady_clock::now().time_since_epoch());
0175         return Error::None;
0176     }
0177     case CommitMode::Test: {
0178         if (!commit->test()) {
0179             qCDebug(KWIN_DRM) << "Atomic test failed!" << strerror(errno);
0180             return errnoToError();
0181         }
0182         return Error::None;
0183     }
0184     default:
0185         Q_UNREACHABLE();
0186     }
0187 }
0188 
0189 DrmPipeline::Error DrmPipeline::prepareAtomicCommit(DrmAtomicCommit *commit, CommitMode mode)
0190 {
0191     if (activePending()) {
0192         if (Error err = prepareAtomicPresentation(commit); err != Error::None) {
0193             return err;
0194         }
0195         if (m_pending.crtc->cursorPlane()) {
0196             prepareAtomicCursor(commit);
0197         }
0198         if (mode == CommitMode::TestAllowModeset || mode == CommitMode::CommitModeset || m_pending.needsModesetProperties) {
0199             if (!prepareAtomicModeset(commit)) {
0200                 return Error::InvalidArguments;
0201             }
0202         }
0203     } else {
0204         prepareAtomicDisable(commit);
0205     }
0206     return Error::None;
0207 }
0208 
0209 static QRect centerBuffer(const QSize &bufferSize, const QSize &modeSize)
0210 {
0211     const double widthScale = bufferSize.width() / double(modeSize.width());
0212     const double heightScale = bufferSize.height() / double(modeSize.height());
0213     if (widthScale > heightScale) {
0214         const QSize size = bufferSize / widthScale;
0215         const uint32_t yOffset = (modeSize.height() - size.height()) / 2;
0216         return QRect(QPoint(0, yOffset), size);
0217     } else {
0218         const QSize size = bufferSize / heightScale;
0219         const uint32_t xOffset = (modeSize.width() - size.width()) / 2;
0220         return QRect(QPoint(xOffset, 0), size);
0221     }
0222 }
0223 
0224 DrmPipeline::Error DrmPipeline::prepareAtomicPresentation(DrmAtomicCommit *commit)
0225 {
0226     commit->setPresentationMode(m_pending.presentationMode);
0227     if (m_connector->contentType.isValid()) {
0228         commit->addEnum(m_connector->contentType, m_pending.contentType);
0229     }
0230 
0231     if (m_pending.crtc->vrrEnabled.isValid()) {
0232         commit->setVrr(m_pending.crtc, m_pending.presentationMode == PresentationMode::AdaptiveSync || m_pending.presentationMode == PresentationMode::AdaptiveAsync);
0233     }
0234     if (m_pending.crtc->gammaLut.isValid()) {
0235         commit->addBlob(m_pending.crtc->gammaLut, m_pending.gamma ? m_pending.gamma->blob() : nullptr);
0236     } else if (m_pending.gamma) {
0237         return Error::InvalidArguments;
0238     }
0239     if (m_pending.crtc->ctm.isValid()) {
0240         commit->addBlob(m_pending.crtc->ctm, m_pending.ctm);
0241     } else if (m_pending.ctm) {
0242         return Error::InvalidArguments;
0243     }
0244 
0245     if (!m_primaryLayer->checkTestBuffer()) {
0246         qCWarning(KWIN_DRM) << "Checking test buffer failed!";
0247         return Error::TestBufferFailed;
0248     }
0249     const auto fb = m_primaryLayer->currentBuffer();
0250     if (!fb) {
0251         return Error::InvalidArguments;
0252     }
0253     const auto primary = m_pending.crtc->primaryPlane();
0254     primary->set(commit, QPoint(0, 0), fb->buffer()->size(), centerBuffer(fb->buffer()->size(), m_pending.mode->size()));
0255     commit->addBuffer(m_pending.crtc->primaryPlane(), fb);
0256     if (fb->buffer()->dmabufAttributes()->format == DRM_FORMAT_NV12) {
0257         if (!primary->colorEncoding.isValid() || !primary->colorRange.isValid()) {
0258             // don't allow NV12 direct scanout if we don't know what the driver will do
0259             return Error::InvalidArguments;
0260         }
0261         commit->addEnum(primary->colorEncoding, DrmPlane::ColorEncoding::BT709_YCbCr);
0262         commit->addEnum(primary->colorRange, DrmPlane::ColorRange::Limited_YCbCr);
0263     }
0264     return Error::None;
0265 }
0266 
0267 static const bool s_vrrCursorWorkaround = qEnvironmentVariableIntValue("KWIN_DRM_NO_VRR_CURSOR_WORKAROUND") == 0;
0268 
0269 void DrmPipeline::prepareAtomicCursor(DrmAtomicCommit *commit)
0270 {
0271     if (s_vrrCursorWorkaround && m_pending.presentationMode != PresentationMode::VSync) {
0272         // trigger a pageflip on the primary plane, as a workaround for https://gitlab.freedesktop.org/drm/amd/-/issues/3034
0273         commit->addProperty(m_pending.crtc->primaryPlane()->srcX, 0);
0274     }
0275     auto plane = m_pending.crtc->cursorPlane();
0276     const auto layer = cursorLayer();
0277     plane->set(commit, QPoint(0, 0), gpu()->cursorSize(), QRect(layer->position().toPoint(), gpu()->cursorSize()));
0278     commit->addProperty(plane->crtcId, layer->isEnabled() ? m_pending.crtc->id() : 0);
0279     commit->addBuffer(plane, layer->isEnabled() ? layer->currentBuffer() : nullptr);
0280     if (plane->vmHotspotX.isValid() && plane->vmHotspotY.isValid()) {
0281         commit->addProperty(plane->vmHotspotX, std::round(layer->hotspot().x()));
0282         commit->addProperty(plane->vmHotspotY, std::round(layer->hotspot().y()));
0283     }
0284 }
0285 
0286 void DrmPipeline::prepareAtomicDisable(DrmAtomicCommit *commit)
0287 {
0288     m_connector->disable(commit);
0289     if (m_pending.crtc) {
0290         m_pending.crtc->disable(commit);
0291         m_pending.crtc->primaryPlane()->disable(commit);
0292         if (auto cursor = m_pending.crtc->cursorPlane()) {
0293             cursor->disable(commit);
0294         }
0295     }
0296 }
0297 
0298 bool DrmPipeline::prepareAtomicModeset(DrmAtomicCommit *commit)
0299 {
0300     commit->addProperty(m_connector->crtcId, m_pending.crtc->id());
0301     if (m_connector->broadcastRGB.isValid()) {
0302         commit->addEnum(m_connector->broadcastRGB, DrmConnector::rgbRangeToBroadcastRgb(m_pending.rgbRange));
0303     }
0304     if (m_connector->linkStatus.isValid()) {
0305         commit->addEnum(m_connector->linkStatus, DrmConnector::LinkStatus::Good);
0306     }
0307     if (m_connector->overscan.isValid()) {
0308         commit->addProperty(m_connector->overscan, m_pending.overscan);
0309     } else if (m_connector->underscan.isValid()) {
0310         const uint32_t hborder = calculateUnderscan();
0311         commit->addEnum(m_connector->underscan, m_pending.overscan != 0 ? DrmConnector::UnderscanOptions::On : DrmConnector::UnderscanOptions::Off);
0312         commit->addProperty(m_connector->underscanVBorder, m_pending.overscan);
0313         commit->addProperty(m_connector->underscanHBorder, hborder);
0314     }
0315     if (m_connector->maxBpc.isValid()) {
0316         uint64_t preferred = 8;
0317         if (auto backend = dynamic_cast<EglGbmBackend *>(gpu()->platform()->renderBackend()); backend && backend->prefer10bpc()) {
0318             preferred = 10;
0319         }
0320         commit->addProperty(m_connector->maxBpc, preferred);
0321     }
0322     if (m_connector->hdrMetadata.isValid()) {
0323         commit->addBlob(m_connector->hdrMetadata, createHdrMetadata(m_pending.colorDescription.transferFunction()));
0324     } else if (m_pending.colorDescription.transferFunction() != NamedTransferFunction::gamma22) {
0325         return false;
0326     }
0327     if (m_pending.colorDescription.colorimetry() == NamedColorimetry::BT2020) {
0328         if (!m_connector->colorspace.isValid() || !m_connector->colorspace.hasEnum(DrmConnector::Colorspace::BT2020_RGB)) {
0329             return false;
0330         }
0331         commit->addEnum(m_connector->colorspace, DrmConnector::Colorspace::BT2020_RGB);
0332     } else if (m_connector->colorspace.isValid()) {
0333         commit->addEnum(m_connector->colorspace, DrmConnector::Colorspace::Default);
0334     }
0335     if (m_connector->scalingMode.isValid() && m_connector->scalingMode.hasEnum(DrmConnector::ScalingMode::None)) {
0336         commit->addEnum(m_connector->scalingMode, DrmConnector::ScalingMode::None);
0337     }
0338 
0339     commit->addProperty(m_pending.crtc->active, 1);
0340     commit->addBlob(m_pending.crtc->modeId, m_pending.mode->blob());
0341     if (m_pending.crtc->degammaLut.isValid()) {
0342         commit->addBlob(m_pending.crtc->degammaLut, nullptr);
0343     }
0344 
0345     const auto primary = m_pending.crtc->primaryPlane();
0346     commit->addProperty(primary->crtcId, m_pending.crtc->id());
0347     if (primary->rotation.isValid()) {
0348         commit->addEnum(primary->rotation, {DrmPlane::Transformation::Rotate0});
0349     }
0350     if (primary->alpha.isValid()) {
0351         commit->addProperty(primary->alpha, 0xFFFF);
0352     }
0353     if (primary->pixelBlendMode.isValid()) {
0354         commit->addEnum(primary->pixelBlendMode, DrmPlane::PixelBlendMode::PreMultiplied);
0355     }
0356     if (const auto cursor = m_pending.crtc->cursorPlane()) {
0357         if (cursor->rotation.isValid()) {
0358             commit->addEnum(cursor->rotation, DrmPlane::Transformations(DrmPlane::Transformation::Rotate0));
0359         }
0360         if (cursor->alpha.isValid()) {
0361             commit->addProperty(cursor->alpha, 0xFFFF);
0362         }
0363         if (cursor->pixelBlendMode.isValid()) {
0364             commit->addEnum(cursor->pixelBlendMode, DrmPlane::PixelBlendMode::PreMultiplied);
0365         }
0366         prepareAtomicCursor(commit);
0367     }
0368     return true;
0369 }
0370 
0371 uint32_t DrmPipeline::calculateUnderscan()
0372 {
0373     const auto size = m_pending.mode->size();
0374     const float aspectRatio = size.width() / static_cast<float>(size.height());
0375     uint32_t hborder = m_pending.overscan * aspectRatio;
0376     if (hborder > 128) {
0377         // overscan only goes from 0-100 so we cut off the 101-128 value range of underscan_vborder
0378         hborder = 128;
0379         m_pending.overscan = 128 / aspectRatio;
0380     }
0381     return hborder;
0382 }
0383 
0384 DrmPipeline::Error DrmPipeline::errnoToError()
0385 {
0386     switch (errno) {
0387     case EINVAL:
0388         return Error::InvalidArguments;
0389     case EBUSY:
0390         return Error::FramePending;
0391     case ENOMEM:
0392         return Error::OutofMemory;
0393     case EACCES:
0394         return Error::NoPermission;
0395     default:
0396         return Error::Unknown;
0397     }
0398 }
0399 
0400 bool DrmPipeline::updateCursor()
0401 {
0402     if (needsModeset() || !m_pending.crtc || !m_pending.active) {
0403         return false;
0404     }
0405     // explicitly check for the cursor plane and not for AMS, as we might not always have one
0406     if (m_pending.crtc->cursorPlane()) {
0407         // test the full state, to take pending commits into account
0408         auto fullState = std::make_unique<DrmAtomicCommit>(QList<DrmPipeline *>{this});
0409         if (prepareAtomicPresentation(fullState.get()) != Error::None) {
0410             return false;
0411         }
0412         prepareAtomicCursor(fullState.get());
0413         if (!fullState->test()) {
0414             return false;
0415         }
0416         // only give the actual state update to the commit thread, so that it can potentially reorder the commits
0417         auto cursorOnly = std::make_unique<DrmAtomicCommit>(QList<DrmPipeline *>{this});
0418         prepareAtomicCursor(cursorOnly.get());
0419         cursorOnly->setCursorOnly(true);
0420         m_commitThread->addCommit(std::move(cursorOnly));
0421         return true;
0422     } else {
0423         return setCursorLegacy();
0424     }
0425 }
0426 
0427 void DrmPipeline::applyPendingChanges()
0428 {
0429     m_next = m_pending;
0430     m_commitThread->setModeInfo(m_pending.mode->refreshRate(), m_pending.mode->vblankTime());
0431     if (m_output) {
0432         m_output->renderLoop()->setPresentationSafetyMargin(m_commitThread->safetyMargin());
0433     }
0434 }
0435 
0436 DrmConnector *DrmPipeline::connector() const
0437 {
0438     return m_connector;
0439 }
0440 
0441 DrmGpu *DrmPipeline::gpu() const
0442 {
0443     return m_connector->gpu();
0444 }
0445 
0446 void DrmPipeline::pageFlipped(std::chrono::nanoseconds timestamp, PageflipType type, PresentationMode mode)
0447 {
0448     m_commitThread->pageFlipped(timestamp);
0449     if (type == PageflipType::Modeset && !activePending()) {
0450         return;
0451     }
0452     if (m_output) {
0453         if (type == PageflipType::Normal || type == PageflipType::Modeset) {
0454             m_output->pageFlipped(timestamp, mode);
0455         } else {
0456             RenderLoopPrivate::get(m_output->renderLoop())->notifyVblank(timestamp);
0457         }
0458     }
0459 }
0460 
0461 void DrmPipeline::setOutput(DrmOutput *output)
0462 {
0463     m_output = output;
0464 }
0465 
0466 DrmOutput *DrmPipeline::output() const
0467 {
0468     return m_output;
0469 }
0470 
0471 QMap<uint32_t, QList<uint64_t>> DrmPipeline::formats() const
0472 {
0473     return m_pending.formats;
0474 }
0475 
0476 QMap<uint32_t, QList<uint64_t>> DrmPipeline::cursorFormats() const
0477 {
0478     if (m_pending.crtc && m_pending.crtc->cursorPlane()) {
0479         return m_pending.crtc->cursorPlane()->formats();
0480     } else {
0481         return legacyCursorFormats;
0482     }
0483 }
0484 
0485 bool DrmPipeline::hasCTM() const
0486 {
0487     return m_pending.crtc && m_pending.crtc->ctm.isValid();
0488 }
0489 
0490 bool DrmPipeline::hasGammaRamp() const
0491 {
0492     if (gpu()->atomicModeSetting()) {
0493         return m_pending.crtc && m_pending.crtc->gammaLut.isValid();
0494     } else {
0495         return m_pending.crtc && m_pending.crtc->gammaRampSize() > 0;
0496     }
0497 }
0498 
0499 bool DrmPipeline::pruneModifier()
0500 {
0501     const DmaBufAttributes *dmabufAttributes = m_primaryLayer->currentBuffer() ? m_primaryLayer->currentBuffer()->buffer()->dmabufAttributes() : nullptr;
0502     if (!dmabufAttributes) {
0503         return false;
0504     }
0505     auto &modifiers = m_pending.formats[dmabufAttributes->format];
0506     if (modifiers == implicitModifier) {
0507         return false;
0508     } else {
0509         modifiers = implicitModifier;
0510         return true;
0511     }
0512 }
0513 
0514 bool DrmPipeline::needsModeset() const
0515 {
0516     return m_pending.needsModeset;
0517 }
0518 
0519 bool DrmPipeline::activePending() const
0520 {
0521     return m_pending.crtc && m_pending.mode && m_pending.active;
0522 }
0523 
0524 void DrmPipeline::revertPendingChanges()
0525 {
0526     m_pending = m_next;
0527 }
0528 
0529 bool DrmPipeline::pageflipsPending() const
0530 {
0531     return m_commitThread->pageflipsPending();
0532 }
0533 
0534 bool DrmPipeline::modesetPresentPending() const
0535 {
0536     return m_modesetPresentPending;
0537 }
0538 
0539 void DrmPipeline::resetModesetPresentPending()
0540 {
0541     m_modesetPresentPending = false;
0542 }
0543 
0544 DrmGammaRamp::DrmGammaRamp(DrmCrtc *crtc, const std::shared_ptr<ColorTransformation> &transformation)
0545     : m_lut(transformation, crtc->gammaRampSize())
0546 {
0547     if (crtc->gpu()->atomicModeSetting()) {
0548         QList<drm_color_lut> atomicLut(m_lut.size());
0549         for (uint32_t i = 0; i < m_lut.size(); i++) {
0550             atomicLut[i].red = m_lut.red()[i];
0551             atomicLut[i].green = m_lut.green()[i];
0552             atomicLut[i].blue = m_lut.blue()[i];
0553         }
0554         m_blob = DrmBlob::create(crtc->gpu(), atomicLut.data(), sizeof(drm_color_lut) * atomicLut.size());
0555     }
0556 }
0557 
0558 const ColorLUT &DrmGammaRamp::lut() const
0559 {
0560     return m_lut;
0561 }
0562 
0563 std::shared_ptr<DrmBlob> DrmGammaRamp::blob() const
0564 {
0565     return m_blob;
0566 }
0567 
0568 DrmCrtc *DrmPipeline::crtc() const
0569 {
0570     return m_pending.crtc;
0571 }
0572 
0573 std::shared_ptr<DrmConnectorMode> DrmPipeline::mode() const
0574 {
0575     return m_pending.mode;
0576 }
0577 
0578 bool DrmPipeline::active() const
0579 {
0580     return m_pending.active;
0581 }
0582 
0583 bool DrmPipeline::enabled() const
0584 {
0585     return m_pending.enabled;
0586 }
0587 
0588 DrmPipelineLayer *DrmPipeline::primaryLayer() const
0589 {
0590     return m_primaryLayer.get();
0591 }
0592 
0593 DrmPipelineLayer *DrmPipeline::cursorLayer() const
0594 {
0595     return m_cursorLayer.get();
0596 }
0597 
0598 DrmPlane::Transformations DrmPipeline::renderOrientation() const
0599 {
0600     return m_pending.renderOrientation;
0601 }
0602 
0603 PresentationMode DrmPipeline::presentationMode() const
0604 {
0605     return m_pending.presentationMode;
0606 }
0607 
0608 uint32_t DrmPipeline::overscan() const
0609 {
0610     return m_pending.overscan;
0611 }
0612 
0613 Output::RgbRange DrmPipeline::rgbRange() const
0614 {
0615     return m_pending.rgbRange;
0616 }
0617 
0618 DrmConnector::DrmContentType DrmPipeline::contentType() const
0619 {
0620     return m_pending.contentType;
0621 }
0622 
0623 const ColorDescription &DrmPipeline::colorDescription() const
0624 {
0625     return m_pending.colorDescription;
0626 }
0627 
0628 const std::shared_ptr<IccProfile> &DrmPipeline::iccProfile() const
0629 {
0630     return m_pending.iccProfile;
0631 }
0632 
0633 void DrmPipeline::setCrtc(DrmCrtc *crtc)
0634 {
0635     if (crtc && m_pending.crtc && crtc->gammaRampSize() != m_pending.crtc->gammaRampSize() && m_pending.colorTransformation) {
0636         m_pending.gamma = std::make_shared<DrmGammaRamp>(crtc, m_pending.colorTransformation);
0637     }
0638     m_pending.crtc = crtc;
0639     if (crtc) {
0640         m_pending.formats = crtc->primaryPlane() ? crtc->primaryPlane()->formats() : legacyFormats;
0641     } else {
0642         m_pending.formats = {};
0643     }
0644 }
0645 
0646 void DrmPipeline::setMode(const std::shared_ptr<DrmConnectorMode> &mode)
0647 {
0648     m_pending.mode = mode;
0649 }
0650 
0651 void DrmPipeline::setActive(bool active)
0652 {
0653     m_pending.active = active;
0654 }
0655 
0656 void DrmPipeline::setEnable(bool enable)
0657 {
0658     m_pending.enabled = enable;
0659 }
0660 
0661 void DrmPipeline::setLayers(const std::shared_ptr<DrmPipelineLayer> &primaryLayer, const std::shared_ptr<DrmPipelineLayer> &cursorLayer)
0662 {
0663     m_primaryLayer = primaryLayer;
0664     m_cursorLayer = cursorLayer;
0665 }
0666 
0667 void DrmPipeline::setRenderOrientation(DrmPlane::Transformations orientation)
0668 {
0669     m_pending.renderOrientation = orientation;
0670 }
0671 
0672 void DrmPipeline::setPresentationMode(PresentationMode mode)
0673 {
0674     m_pending.presentationMode = mode;
0675 }
0676 
0677 void DrmPipeline::setOverscan(uint32_t overscan)
0678 {
0679     m_pending.overscan = overscan;
0680 }
0681 
0682 void DrmPipeline::setRgbRange(Output::RgbRange range)
0683 {
0684     m_pending.rgbRange = range;
0685 }
0686 
0687 void DrmPipeline::setGammaRamp(const std::shared_ptr<ColorTransformation> &transformation)
0688 {
0689     m_pending.colorTransformation = transformation;
0690     if (transformation) {
0691         m_pending.gamma = std::make_shared<DrmGammaRamp>(m_pending.crtc, transformation);
0692     } else {
0693         m_pending.gamma.reset();
0694     }
0695 }
0696 
0697 static uint64_t doubleToFixed(double value)
0698 {
0699     // ctm values are in S31.32 sign-magnitude format
0700     uint64_t ret = std::abs(value) * (1ull << 32);
0701     if (value < 0) {
0702         ret |= 1ull << 63;
0703     }
0704     return ret;
0705 }
0706 
0707 void DrmPipeline::setCTM(const QMatrix3x3 &ctm)
0708 {
0709     if (ctm.isIdentity()) {
0710         m_pending.ctm.reset();
0711     } else {
0712         drm_color_ctm blob = {
0713             .matrix = {
0714                 doubleToFixed(ctm(0, 0)), doubleToFixed(ctm(1, 0)), doubleToFixed(ctm(2, 0)),
0715                 doubleToFixed(ctm(0, 1)), doubleToFixed(ctm(1, 1)), doubleToFixed(ctm(2, 1)),
0716                 doubleToFixed(ctm(0, 2)), doubleToFixed(ctm(1, 2)), doubleToFixed(ctm(2, 2))},
0717         };
0718         m_pending.ctm = DrmBlob::create(gpu(), &blob, sizeof(blob));
0719     }
0720 }
0721 
0722 void DrmPipeline::setColorDescription(const ColorDescription &description)
0723 {
0724     m_pending.colorDescription = description;
0725 }
0726 
0727 void DrmPipeline::setContentType(DrmConnector::DrmContentType type)
0728 {
0729     m_pending.contentType = type;
0730 }
0731 
0732 void DrmPipeline::setIccProfile(const std::shared_ptr<IccProfile> &profile)
0733 {
0734     if (m_pending.iccProfile != profile) {
0735         m_pending.iccProfile = profile;
0736     }
0737 }
0738 
0739 std::shared_ptr<DrmBlob> DrmPipeline::createHdrMetadata(NamedTransferFunction transferFunction) const
0740 {
0741     if (transferFunction != NamedTransferFunction::PerceptualQuantizer) {
0742         // for sRGB / gamma 2.2, don't send any metadata, to ensure the non-HDR experience stays the same
0743         return nullptr;
0744     }
0745     if (!m_connector->edid()->supportsPQ()) {
0746         return nullptr;
0747     }
0748     const auto colorimetry = m_connector->edid()->colorimetry().value_or(Colorimetry::fromName(NamedColorimetry::BT709));
0749     const auto to16Bit = [](float value) {
0750         return uint16_t(std::round(value / 0.00002));
0751     };
0752     hdr_output_metadata data{
0753         .metadata_type = 0,
0754         .hdmi_metadata_type1 = hdr_metadata_infoframe{
0755             // eotf types (from CTA-861-G page 85):
0756             // - 0: traditional gamma, SDR
0757             // - 1: traditional gamma, HDR
0758             // - 2: SMPTE ST2084
0759             // - 3: hybrid Log-Gamma based on BT.2100-0
0760             // - 4-7: reserved
0761             .eotf = uint8_t(2),
0762             // there's only one type. 1-7 are reserved for future use
0763             .metadata_type = 0,
0764             // in 0.00002 nits
0765             .display_primaries = {
0766                 {to16Bit(colorimetry.red().x()), to16Bit(colorimetry.red().y())},
0767                 {to16Bit(colorimetry.green().x()), to16Bit(colorimetry.green().y())},
0768                 {to16Bit(colorimetry.blue().x()), to16Bit(colorimetry.blue().y())},
0769             },
0770             .white_point = {to16Bit(colorimetry.white().x()), to16Bit(colorimetry.white().y())},
0771             // in nits
0772             .max_display_mastering_luminance = uint16_t(std::round(m_connector->edid()->desiredMaxFrameAverageLuminance().value_or(0))),
0773             // in 0.0001 nits
0774             .min_display_mastering_luminance = uint16_t(std::round(m_connector->edid()->desiredMinLuminance() * 10000)),
0775             // in nits
0776             .max_cll = uint16_t(std::round(m_connector->edid()->desiredMaxFrameAverageLuminance().value_or(0))),
0777             .max_fall = uint16_t(std::round(m_connector->edid()->desiredMaxFrameAverageLuminance().value_or(0))),
0778         },
0779     };
0780     return DrmBlob::create(gpu(), &data, sizeof(data));
0781 }
0782 
0783 std::chrono::nanoseconds DrmPipeline::presentationDeadline() const
0784 {
0785     return m_commitThread->safetyMargin();
0786 }
0787 }