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 }