KWin
Loading...
Searching...
No Matches
drm_pipeline.cpp
Go to the documentation of this file.
1/*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 2021 Xaver Hugl <xaver.hugl@gmail.com>
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8*/
9
10#include "drm_pipeline.h"
11
12#include <errno.h>
13
14#include "core/iccprofile.h"
15#include "core/session.h"
16#include "drm_backend.h"
17#include "drm_buffer.h"
18#include "drm_commit.h"
19#include "drm_commit_thread.h"
20#include "drm_connector.h"
21#include "drm_crtc.h"
22#include "drm_egl_backend.h"
23#include "drm_gpu.h"
24#include "drm_layer.h"
25#include "drm_logging.h"
26#include "drm_output.h"
27#include "drm_plane.h"
28
29#include <drm_fourcc.h>
30#include <gbm.h>
31
32using namespace std::literals;
33
34namespace KWin
35{
36
37static const QList<uint64_t> implicitModifier = {DRM_FORMAT_MOD_INVALID};
38static const QMap<uint32_t, QList<uint64_t>> legacyFormats = {{DRM_FORMAT_XRGB8888, implicitModifier}};
39static const QMap<uint32_t, QList<uint64_t>> legacyCursorFormats = {{DRM_FORMAT_ARGB8888, implicitModifier}};
40
42 : m_connector(conn)
43 , m_commitThread(std::make_unique<DrmCommitThread>(conn->gpu(), conn->connectorName()))
44{
45 QObject::connect(m_commitThread.get(), &DrmCommitThread::commitFailed, [this]() {
46 if (m_output) {
47 m_output->frameFailed();
48 }
49 });
50}
51
52DrmPipeline::~DrmPipeline()
53{
54 if (pageflipsPending()) {
55 gpu()->waitIdle();
56 }
57}
58
59bool DrmPipeline::testScanout()
60{
61 if (gpu()->needsModeset()) {
62 return false;
63 }
64 if (gpu()->atomicModeSetting()) {
65 return commitPipelines({this}, CommitMode::Test) == Error::None;
66 } else {
67 if (m_primaryLayer->currentBuffer()->buffer()->size() != m_pending.mode->size()) {
68 // scaling isn't supported with the legacy API
69 return false;
70 }
71 // no other way to test than to do it.
72 // As we only have a maximum of one test per scanout cycle, this is fine
73 return presentLegacy() == Error::None;
74 }
75}
76
77DrmPipeline::Error DrmPipeline::present()
78{
79 Q_ASSERT(m_pending.crtc);
80 if (gpu()->atomicModeSetting()) {
81 // test the full state, to take pending commits into account
82 auto fullState = std::make_unique<DrmAtomicCommit>(QList<DrmPipeline *>{this});
83 if (Error err = prepareAtomicCommit(fullState.get(), CommitMode::Test); err != Error::None) {
84 return err;
85 }
86 if (!fullState->test()) {
87 return errnoToError();
88 }
89 // only give the actual state update to the commit thread, so that it can potentially reorder the commits
90 auto primaryPlaneUpdate = std::make_unique<DrmAtomicCommit>(QList<DrmPipeline *>{this});
91 if (Error err = prepareAtomicPresentation(primaryPlaneUpdate.get()); err != Error::None) {
92 return err;
93 }
94 if (m_pending.needsModesetProperties && !prepareAtomicModeset(primaryPlaneUpdate.get())) {
95 return Error::InvalidArguments;
96 }
97 m_next.needsModesetProperties = m_pending.needsModesetProperties = false;
98 m_commitThread->addCommit(std::move(primaryPlaneUpdate));
99 return Error::None;
100 } else {
101 if (m_primaryLayer->hasDirectScanoutBuffer()) {
102 // already presented
103 return Error::None;
104 }
105 return presentLegacy();
106 }
107}
108
109bool DrmPipeline::maybeModeset()
110{
111 m_modesetPresentPending = true;
112 return gpu()->maybeModeset();
113}
114
115DrmPipeline::Error DrmPipeline::commitPipelines(const QList<DrmPipeline *> &pipelines, CommitMode mode, const QList<DrmObject *> &unusedObjects)
116{
117 Q_ASSERT(!pipelines.isEmpty());
118 if (pipelines[0]->gpu()->atomicModeSetting()) {
119 return commitPipelinesAtomic(pipelines, mode, unusedObjects);
120 } else {
121 return commitPipelinesLegacy(pipelines, mode, unusedObjects);
122 }
123}
124
125DrmPipeline::Error DrmPipeline::commitPipelinesAtomic(const QList<DrmPipeline *> &pipelines, CommitMode mode, const QList<DrmObject *> &unusedObjects)
126{
127 auto commit = std::make_unique<DrmAtomicCommit>(pipelines);
128 if (mode == CommitMode::Test) {
129 // if there's a modeset pending, the tests on top of that state
130 // also have to allow modesets or they'll always fail
131 const bool wantsModeset = std::any_of(pipelines.begin(), pipelines.end(), [](DrmPipeline *pipeline) {
132 return pipeline->needsModeset();
133 });
134 if (wantsModeset) {
135 mode = CommitMode::TestAllowModeset;
136 }
137 }
138 for (const auto &pipeline : pipelines) {
139 if (Error err = pipeline->prepareAtomicCommit(commit.get(), mode); err != Error::None) {
140 return err;
141 }
142 }
143 for (const auto &unused : unusedObjects) {
144 unused->disable(commit.get());
145 }
146 switch (mode) {
147 case CommitMode::TestAllowModeset: {
148 if (!commit->testAllowModeset()) {
149 qCDebug(KWIN_DRM) << "Atomic modeset test failed!" << strerror(errno);
150 return errnoToError();
151 }
152 const bool withoutModeset = std::all_of(pipelines.begin(), pipelines.end(), [](DrmPipeline *pipeline) {
153 auto commit = std::make_unique<DrmAtomicCommit>(QVector<DrmPipeline *>{pipeline});
154 return pipeline->prepareAtomicCommit(commit.get(), CommitMode::TestAllowModeset) == Error::None && commit->test();
155 });
156 for (const auto &pipeline : pipelines) {
157 pipeline->m_pending.needsModeset = !withoutModeset;
158 pipeline->m_pending.needsModesetProperties = true;
159 }
160 return Error::None;
161 }
162 case CommitMode::CommitModeset: {
163 // The kernel fails commits with DRM_MODE_PAGE_FLIP_EVENT when a crtc is disabled in the commit
164 // and already was disabled before, to work around some quirks in old userspace.
165 // Instead of using DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK, do the modeset in a blocking
166 // fashion without page flip events and trigger the pageflip notification directly
167 if (!commit->commitModeset()) {
168 qCCritical(KWIN_DRM) << "Atomic modeset commit failed!" << strerror(errno);
169 return errnoToError();
170 }
171 for (const auto pipeline : pipelines) {
172 pipeline->m_next.needsModeset = pipeline->m_pending.needsModeset = false;
173 }
174 commit->pageFlipped(std::chrono::steady_clock::now().time_since_epoch());
175 return Error::None;
176 }
177 case CommitMode::Test: {
178 if (!commit->test()) {
179 qCDebug(KWIN_DRM) << "Atomic test failed!" << strerror(errno);
180 return errnoToError();
181 }
182 return Error::None;
183 }
184 default:
185 Q_UNREACHABLE();
186 }
187}
188
189DrmPipeline::Error DrmPipeline::prepareAtomicCommit(DrmAtomicCommit *commit, CommitMode mode)
190{
191 if (activePending()) {
192 if (Error err = prepareAtomicPresentation(commit); err != Error::None) {
193 return err;
194 }
195 if (m_pending.crtc->cursorPlane()) {
196 prepareAtomicCursor(commit);
197 }
198 if (mode == CommitMode::TestAllowModeset || mode == CommitMode::CommitModeset || m_pending.needsModesetProperties) {
199 if (!prepareAtomicModeset(commit)) {
200 return Error::InvalidArguments;
201 }
202 }
203 } else {
204 prepareAtomicDisable(commit);
205 }
206 return Error::None;
207}
208
209static QRect centerBuffer(const QSize &bufferSize, const QSize &modeSize)
210{
211 const double widthScale = bufferSize.width() / double(modeSize.width());
212 const double heightScale = bufferSize.height() / double(modeSize.height());
213 if (widthScale > heightScale) {
214 const QSize size = bufferSize / widthScale;
215 const uint32_t yOffset = (modeSize.height() - size.height()) / 2;
216 return QRect(QPoint(0, yOffset), size);
217 } else {
218 const QSize size = bufferSize / heightScale;
219 const uint32_t xOffset = (modeSize.width() - size.width()) / 2;
220 return QRect(QPoint(xOffset, 0), size);
221 }
222}
223
224DrmPipeline::Error DrmPipeline::prepareAtomicPresentation(DrmAtomicCommit *commit)
225{
226 commit->setPresentationMode(m_pending.presentationMode);
227 if (m_connector->contentType.isValid()) {
228 commit->addEnum(m_connector->contentType, m_pending.contentType);
229 }
230
231 if (m_pending.crtc->vrrEnabled.isValid()) {
232 commit->setVrr(m_pending.crtc, m_pending.presentationMode == PresentationMode::AdaptiveSync || m_pending.presentationMode == PresentationMode::AdaptiveAsync);
233 }
234 if (m_pending.crtc->gammaLut.isValid()) {
235 commit->addBlob(m_pending.crtc->gammaLut, m_pending.gamma ? m_pending.gamma->blob() : nullptr);
236 } else if (m_pending.gamma) {
237 return Error::InvalidArguments;
238 }
239 if (m_pending.crtc->ctm.isValid()) {
240 commit->addBlob(m_pending.crtc->ctm, m_pending.ctm);
241 } else if (m_pending.ctm) {
242 return Error::InvalidArguments;
243 }
244
245 if (!m_primaryLayer->checkTestBuffer()) {
246 qCWarning(KWIN_DRM) << "Checking test buffer failed!";
247 return Error::TestBufferFailed;
248 }
249 const auto fb = m_primaryLayer->currentBuffer();
250 if (!fb) {
251 return Error::InvalidArguments;
252 }
253 const auto primary = m_pending.crtc->primaryPlane();
254 primary->set(commit, QPoint(0, 0), fb->buffer()->size(), centerBuffer(fb->buffer()->size(), m_pending.mode->size()));
255 commit->addBuffer(m_pending.crtc->primaryPlane(), fb);
256 if (fb->buffer()->dmabufAttributes()->format == DRM_FORMAT_NV12) {
257 if (!primary->colorEncoding.isValid() || !primary->colorRange.isValid()) {
258 // don't allow NV12 direct scanout if we don't know what the driver will do
259 return Error::InvalidArguments;
260 }
261 commit->addEnum(primary->colorEncoding, DrmPlane::ColorEncoding::BT709_YCbCr);
262 commit->addEnum(primary->colorRange, DrmPlane::ColorRange::Limited_YCbCr);
263 }
264 return Error::None;
265}
266
267static const bool s_vrrCursorWorkaround = qEnvironmentVariableIntValue("KWIN_DRM_NO_VRR_CURSOR_WORKAROUND") == 0;
268
269void DrmPipeline::prepareAtomicCursor(DrmAtomicCommit *commit)
270{
271 if (s_vrrCursorWorkaround && m_pending.presentationMode != PresentationMode::VSync) {
272 // trigger a pageflip on the primary plane, as a workaround for https://gitlab.freedesktop.org/drm/amd/-/issues/3034
273 commit->addProperty(m_pending.crtc->primaryPlane()->srcX, 0);
274 }
275 auto plane = m_pending.crtc->cursorPlane();
276 const auto layer = cursorLayer();
277 plane->set(commit, QPoint(0, 0), gpu()->cursorSize(), QRect(layer->position().toPoint(), gpu()->cursorSize()));
278 commit->addProperty(plane->crtcId, layer->isEnabled() ? m_pending.crtc->id() : 0);
279 commit->addBuffer(plane, layer->isEnabled() ? layer->currentBuffer() : nullptr);
280 if (plane->vmHotspotX.isValid() && plane->vmHotspotY.isValid()) {
281 commit->addProperty(plane->vmHotspotX, std::round(layer->hotspot().x()));
282 commit->addProperty(plane->vmHotspotY, std::round(layer->hotspot().y()));
283 }
284}
285
286void DrmPipeline::prepareAtomicDisable(DrmAtomicCommit *commit)
287{
288 m_connector->disable(commit);
289 if (m_pending.crtc) {
290 m_pending.crtc->disable(commit);
291 m_pending.crtc->primaryPlane()->disable(commit);
292 if (auto cursor = m_pending.crtc->cursorPlane()) {
293 cursor->disable(commit);
294 }
295 }
296}
297
298bool DrmPipeline::prepareAtomicModeset(DrmAtomicCommit *commit)
299{
300 commit->addProperty(m_connector->crtcId, m_pending.crtc->id());
301 if (m_connector->broadcastRGB.isValid()) {
302 commit->addEnum(m_connector->broadcastRGB, DrmConnector::rgbRangeToBroadcastRgb(m_pending.rgbRange));
303 }
304 if (m_connector->linkStatus.isValid()) {
305 commit->addEnum(m_connector->linkStatus, DrmConnector::LinkStatus::Good);
306 }
307 if (m_connector->overscan.isValid()) {
308 commit->addProperty(m_connector->overscan, m_pending.overscan);
309 } else if (m_connector->underscan.isValid()) {
310 const uint32_t hborder = calculateUnderscan();
311 commit->addEnum(m_connector->underscan, m_pending.overscan != 0 ? DrmConnector::UnderscanOptions::On : DrmConnector::UnderscanOptions::Off);
312 commit->addProperty(m_connector->underscanVBorder, m_pending.overscan);
313 commit->addProperty(m_connector->underscanHBorder, hborder);
314 }
315 if (m_connector->maxBpc.isValid()) {
316 uint64_t preferred = 8;
317 if (auto backend = dynamic_cast<EglGbmBackend *>(gpu()->platform()->renderBackend()); backend && backend->prefer10bpc()) {
318 preferred = 10;
319 }
320 commit->addProperty(m_connector->maxBpc, preferred);
321 }
322 if (m_connector->hdrMetadata.isValid()) {
323 commit->addBlob(m_connector->hdrMetadata, createHdrMetadata(m_pending.colorDescription.transferFunction()));
324 } else if (m_pending.colorDescription.transferFunction() != NamedTransferFunction::gamma22) {
325 return false;
326 }
327 if (m_pending.colorDescription.colorimetry() == NamedColorimetry::BT2020) {
328 if (!m_connector->colorspace.isValid() || !m_connector->colorspace.hasEnum(DrmConnector::Colorspace::BT2020_RGB)) {
329 return false;
330 }
331 commit->addEnum(m_connector->colorspace, DrmConnector::Colorspace::BT2020_RGB);
332 } else if (m_connector->colorspace.isValid()) {
333 commit->addEnum(m_connector->colorspace, DrmConnector::Colorspace::Default);
334 }
335 if (m_connector->scalingMode.isValid() && m_connector->scalingMode.hasEnum(DrmConnector::ScalingMode::None)) {
336 commit->addEnum(m_connector->scalingMode, DrmConnector::ScalingMode::None);
337 }
338
339 commit->addProperty(m_pending.crtc->active, 1);
340 commit->addBlob(m_pending.crtc->modeId, m_pending.mode->blob());
341 if (m_pending.crtc->degammaLut.isValid()) {
342 commit->addBlob(m_pending.crtc->degammaLut, nullptr);
343 }
344
345 const auto primary = m_pending.crtc->primaryPlane();
346 commit->addProperty(primary->crtcId, m_pending.crtc->id());
347 if (primary->rotation.isValid()) {
348 commit->addEnum(primary->rotation, {DrmPlane::Transformation::Rotate0});
349 }
350 if (primary->alpha.isValid()) {
351 commit->addProperty(primary->alpha, 0xFFFF);
352 }
353 if (primary->pixelBlendMode.isValid()) {
354 commit->addEnum(primary->pixelBlendMode, DrmPlane::PixelBlendMode::PreMultiplied);
355 }
356 if (const auto cursor = m_pending.crtc->cursorPlane()) {
357 if (cursor->rotation.isValid()) {
358 commit->addEnum(cursor->rotation, DrmPlane::Transformations(DrmPlane::Transformation::Rotate0));
359 }
360 if (cursor->alpha.isValid()) {
361 commit->addProperty(cursor->alpha, 0xFFFF);
362 }
363 if (cursor->pixelBlendMode.isValid()) {
364 commit->addEnum(cursor->pixelBlendMode, DrmPlane::PixelBlendMode::PreMultiplied);
365 }
366 prepareAtomicCursor(commit);
367 }
368 return true;
369}
370
371uint32_t DrmPipeline::calculateUnderscan()
372{
373 const auto size = m_pending.mode->size();
374 const float aspectRatio = size.width() / static_cast<float>(size.height());
375 uint32_t hborder = m_pending.overscan * aspectRatio;
376 if (hborder > 128) {
377 // overscan only goes from 0-100 so we cut off the 101-128 value range of underscan_vborder
378 hborder = 128;
379 m_pending.overscan = 128 / aspectRatio;
380 }
381 return hborder;
382}
383
384DrmPipeline::Error DrmPipeline::errnoToError()
385{
386 switch (errno) {
387 case EINVAL:
388 return Error::InvalidArguments;
389 case EBUSY:
390 return Error::FramePending;
391 case ENOMEM:
392 return Error::OutofMemory;
393 case EACCES:
394 return Error::NoPermission;
395 default:
396 return Error::Unknown;
397 }
398}
399
400bool DrmPipeline::updateCursor()
401{
402 if (needsModeset() || !m_pending.crtc || !m_pending.active) {
403 return false;
404 }
405 // explicitly check for the cursor plane and not for AMS, as we might not always have one
406 if (m_pending.crtc->cursorPlane()) {
407 // test the full state, to take pending commits into account
408 auto fullState = std::make_unique<DrmAtomicCommit>(QList<DrmPipeline *>{this});
409 if (prepareAtomicPresentation(fullState.get()) != Error::None) {
410 return false;
411 }
412 prepareAtomicCursor(fullState.get());
413 if (!fullState->test()) {
414 return false;
415 }
416 // only give the actual state update to the commit thread, so that it can potentially reorder the commits
417 auto cursorOnly = std::make_unique<DrmAtomicCommit>(QList<DrmPipeline *>{this});
418 prepareAtomicCursor(cursorOnly.get());
419 cursorOnly->setCursorOnly(true);
420 m_commitThread->addCommit(std::move(cursorOnly));
421 return true;
422 } else {
423 return setCursorLegacy();
424 }
425}
426
427void DrmPipeline::applyPendingChanges()
428{
429 m_next = m_pending;
430 m_commitThread->setModeInfo(m_pending.mode->refreshRate(), m_pending.mode->vblankTime());
431 if (m_output) {
432 m_output->renderLoop()->setPresentationSafetyMargin(m_commitThread->safetyMargin());
433 }
434}
435
436DrmConnector *DrmPipeline::connector() const
437{
438 return m_connector;
439}
440
441DrmGpu *DrmPipeline::gpu() const
442{
443 return m_connector->gpu();
444}
445
446void DrmPipeline::pageFlipped(std::chrono::nanoseconds timestamp, PageflipType type, PresentationMode mode)
447{
448 m_commitThread->pageFlipped(timestamp);
449 if (type == PageflipType::Modeset && !activePending()) {
450 return;
451 }
452 if (m_output) {
453 if (type == PageflipType::Normal || type == PageflipType::Modeset) {
454 m_output->pageFlipped(timestamp, mode);
455 } else {
456 RenderLoopPrivate::get(m_output->renderLoop())->notifyVblank(timestamp);
457 }
458 }
459}
460
461void DrmPipeline::setOutput(DrmOutput *output)
462{
463 m_output = output;
464}
465
466DrmOutput *DrmPipeline::output() const
467{
468 return m_output;
469}
470
471QMap<uint32_t, QList<uint64_t>> DrmPipeline::formats() const
472{
473 return m_pending.formats;
474}
475
476QMap<uint32_t, QList<uint64_t>> DrmPipeline::cursorFormats() const
477{
478 if (m_pending.crtc && m_pending.crtc->cursorPlane()) {
479 return m_pending.crtc->cursorPlane()->formats();
480 } else {
481 return legacyCursorFormats;
482 }
483}
484
485bool DrmPipeline::hasCTM() const
486{
487 return m_pending.crtc && m_pending.crtc->ctm.isValid();
488}
489
490bool DrmPipeline::hasGammaRamp() const
491{
492 if (gpu()->atomicModeSetting()) {
493 return m_pending.crtc && m_pending.crtc->gammaLut.isValid();
494 } else {
495 return m_pending.crtc && m_pending.crtc->gammaRampSize() > 0;
496 }
497}
498
499bool DrmPipeline::pruneModifier()
500{
501 const DmaBufAttributes *dmabufAttributes = m_primaryLayer->currentBuffer() ? m_primaryLayer->currentBuffer()->buffer()->dmabufAttributes() : nullptr;
502 if (!dmabufAttributes) {
503 return false;
504 }
505 auto &modifiers = m_pending.formats[dmabufAttributes->format];
506 if (modifiers == implicitModifier) {
507 return false;
508 } else {
509 modifiers = implicitModifier;
510 return true;
511 }
512}
513
514bool DrmPipeline::needsModeset() const
515{
516 return m_pending.needsModeset;
517}
518
519bool DrmPipeline::activePending() const
520{
521 return m_pending.crtc && m_pending.mode && m_pending.active;
522}
523
524void DrmPipeline::revertPendingChanges()
525{
526 m_pending = m_next;
527}
528
529bool DrmPipeline::pageflipsPending() const
530{
531 return m_commitThread->pageflipsPending();
532}
533
534bool DrmPipeline::modesetPresentPending() const
535{
536 return m_modesetPresentPending;
537}
538
539void DrmPipeline::resetModesetPresentPending()
540{
541 m_modesetPresentPending = false;
542}
543
544DrmGammaRamp::DrmGammaRamp(DrmCrtc *crtc, const std::shared_ptr<ColorTransformation> &transformation)
545 : m_lut(transformation, crtc->gammaRampSize())
546{
547 if (crtc->gpu()->atomicModeSetting()) {
548 QList<drm_color_lut> atomicLut(m_lut.size());
549 for (uint32_t i = 0; i < m_lut.size(); i++) {
550 atomicLut[i].red = m_lut.red()[i];
551 atomicLut[i].green = m_lut.green()[i];
552 atomicLut[i].blue = m_lut.blue()[i];
553 }
554 m_blob = DrmBlob::create(crtc->gpu(), atomicLut.data(), sizeof(drm_color_lut) * atomicLut.size());
555 }
556}
557
559{
560 return m_lut;
561}
562
563std::shared_ptr<DrmBlob> DrmGammaRamp::blob() const
564{
565 return m_blob;
566}
567
569{
570 return m_pending.crtc;
571}
572
573std::shared_ptr<DrmConnectorMode> DrmPipeline::mode() const
574{
575 return m_pending.mode;
576}
577
579{
580 return m_pending.active;
581}
582
584{
585 return m_pending.enabled;
586}
587
589{
590 return m_primaryLayer.get();
591}
592
594{
595 return m_cursorLayer.get();
596}
597
598DrmPlane::Transformations DrmPipeline::renderOrientation() const
599{
600 return m_pending.renderOrientation;
601}
602
604{
605 return m_pending.presentationMode;
606}
607
608uint32_t DrmPipeline::overscan() const
609{
610 return m_pending.overscan;
611}
612
614{
615 return m_pending.rgbRange;
616}
617
619{
620 return m_pending.contentType;
621}
622
624{
625 return m_pending.colorDescription;
626}
627
628const std::shared_ptr<IccProfile> &DrmPipeline::iccProfile() const
629{
630 return m_pending.iccProfile;
631}
632
634{
635 if (crtc && m_pending.crtc && crtc->gammaRampSize() != m_pending.crtc->gammaRampSize() && m_pending.colorTransformation) {
636 m_pending.gamma = std::make_shared<DrmGammaRamp>(crtc, m_pending.colorTransformation);
637 }
638 m_pending.crtc = crtc;
639 if (crtc) {
640 m_pending.formats = crtc->primaryPlane() ? crtc->primaryPlane()->formats() : legacyFormats;
641 } else {
642 m_pending.formats = {};
643 }
644}
645
646void DrmPipeline::setMode(const std::shared_ptr<DrmConnectorMode> &mode)
647{
648 m_pending.mode = mode;
649}
650
651void DrmPipeline::setActive(bool active)
652{
653 m_pending.active = active;
654}
655
656void DrmPipeline::setEnable(bool enable)
657{
658 m_pending.enabled = enable;
659}
660
661void DrmPipeline::setLayers(const std::shared_ptr<DrmPipelineLayer> &primaryLayer, const std::shared_ptr<DrmPipelineLayer> &cursorLayer)
662{
663 m_primaryLayer = primaryLayer;
664 m_cursorLayer = cursorLayer;
665}
666
667void DrmPipeline::setRenderOrientation(DrmPlane::Transformations orientation)
668{
669 m_pending.renderOrientation = orientation;
670}
671
673{
674 m_pending.presentationMode = mode;
675}
676
677void DrmPipeline::setOverscan(uint32_t overscan)
678{
679 m_pending.overscan = overscan;
680}
681
683{
684 m_pending.rgbRange = range;
685}
686
687void DrmPipeline::setGammaRamp(const std::shared_ptr<ColorTransformation> &transformation)
688{
689 m_pending.colorTransformation = transformation;
690 if (transformation) {
691 m_pending.gamma = std::make_shared<DrmGammaRamp>(m_pending.crtc, transformation);
692 } else {
693 m_pending.gamma.reset();
694 }
695}
696
697static uint64_t doubleToFixed(double value)
698{
699 // ctm values are in S31.32 sign-magnitude format
700 uint64_t ret = std::abs(value) * (1ull << 32);
701 if (value < 0) {
702 ret |= 1ull << 63;
703 }
704 return ret;
705}
706
707void DrmPipeline::setCTM(const QMatrix3x3 &ctm)
708{
709 if (ctm.isIdentity()) {
710 m_pending.ctm.reset();
711 } else {
712 drm_color_ctm blob = {
713 .matrix = {
714 doubleToFixed(ctm(0, 0)), doubleToFixed(ctm(1, 0)), doubleToFixed(ctm(2, 0)),
715 doubleToFixed(ctm(0, 1)), doubleToFixed(ctm(1, 1)), doubleToFixed(ctm(2, 1)),
716 doubleToFixed(ctm(0, 2)), doubleToFixed(ctm(1, 2)), doubleToFixed(ctm(2, 2))},
717 };
718 m_pending.ctm = DrmBlob::create(gpu(), &blob, sizeof(blob));
719 }
720}
721
723{
724 m_pending.colorDescription = description;
725}
726
728{
729 m_pending.contentType = type;
730}
731
732void DrmPipeline::setIccProfile(const std::shared_ptr<IccProfile> &profile)
733{
734 if (m_pending.iccProfile != profile) {
735 m_pending.iccProfile = profile;
736 }
737}
738
739std::shared_ptr<DrmBlob> DrmPipeline::createHdrMetadata(NamedTransferFunction transferFunction) const
740{
741 if (transferFunction != NamedTransferFunction::PerceptualQuantizer) {
742 // for sRGB / gamma 2.2, don't send any metadata, to ensure the non-HDR experience stays the same
743 return nullptr;
744 }
745 if (!m_connector->edid()->supportsPQ()) {
746 return nullptr;
747 }
748 const auto colorimetry = m_connector->edid()->colorimetry().value_or(Colorimetry::fromName(NamedColorimetry::BT709));
749 const auto to16Bit = [](float value) {
750 return uint16_t(std::round(value / 0.00002));
751 };
752 hdr_output_metadata data{
753 .metadata_type = 0,
754 .hdmi_metadata_type1 = hdr_metadata_infoframe{
755 // eotf types (from CTA-861-G page 85):
756 // - 0: traditional gamma, SDR
757 // - 1: traditional gamma, HDR
758 // - 2: SMPTE ST2084
759 // - 3: hybrid Log-Gamma based on BT.2100-0
760 // - 4-7: reserved
761 .eotf = uint8_t(2),
762 // there's only one type. 1-7 are reserved for future use
763 .metadata_type = 0,
764 // in 0.00002 nits
765 .display_primaries = {
766 {to16Bit(colorimetry.red().x()), to16Bit(colorimetry.red().y())},
767 {to16Bit(colorimetry.green().x()), to16Bit(colorimetry.green().y())},
768 {to16Bit(colorimetry.blue().x()), to16Bit(colorimetry.blue().y())},
769 },
770 .white_point = {to16Bit(colorimetry.white().x()), to16Bit(colorimetry.white().y())},
771 // in nits
772 .max_display_mastering_luminance = uint16_t(std::round(m_connector->edid()->desiredMaxFrameAverageLuminance().value_or(0))),
773 // in 0.0001 nits
774 .min_display_mastering_luminance = uint16_t(std::round(m_connector->edid()->desiredMinLuminance() * 10000)),
775 // in nits
776 .max_cll = uint16_t(std::round(m_connector->edid()->desiredMaxFrameAverageLuminance().value_or(0))),
777 .max_fall = uint16_t(std::round(m_connector->edid()->desiredMaxFrameAverageLuminance().value_or(0))),
778 },
779 };
780 return DrmBlob::create(gpu(), &data, sizeof(data));
781}
782
783std::chrono::nanoseconds DrmPipeline::presentationDeadline() const
784{
785 return m_commitThread->safetyMargin();
786}
787}
uint16_t * blue() const
Definition colorlut.cpp:36
uint16_t * red() const
Definition colorlut.cpp:26
size_t size() const
Definition colorlut.cpp:41
uint16_t * green() const
Definition colorlut.cpp:31
static const Colorimetry & fromName(NamedColorimetry name)
static std::shared_ptr< DrmBlob > create(DrmGpu *gpu, const void *data, uint32_t dataSize)
Definition drm_blob.cpp:33
const Edid * edid() const
DrmPlane * primaryPlane() const
Definition drm_crtc.cpp:87
int gammaRampSize() const
Definition drm_crtc.cpp:76
std::shared_ptr< DrmBlob > blob() const
const ColorLUT & lut() const
bool atomicModeSetting() const
Definition drm_gpu.cpp:658
DrmGpu * gpu() const
Output::RgbRange rgbRange() const
DrmPipelineLayer * cursorLayer() const
uint32_t overscan() const
void setContentType(DrmConnector::DrmContentType type)
PresentationMode presentationMode() const
void setMode(const std::shared_ptr< DrmConnectorMode > &mode)
const std::shared_ptr< IccProfile > & iccProfile() const
bool active() const
void setLayers(const std::shared_ptr< DrmPipelineLayer > &primaryLayer, const std::shared_ptr< DrmPipelineLayer > &cursorLayer)
void setRgbRange(Output::RgbRange range)
DrmGpu * gpu() const
const ColorDescription & colorDescription() const
std::chrono::nanoseconds presentationDeadline() const
void setEnable(bool enable)
void setCTM(const QMatrix3x3 &ctm)
void setActive(bool active)
void setCrtc(DrmCrtc *crtc)
bool enabled() const
DrmPlane::Transformations renderOrientation() const
void setColorDescription(const ColorDescription &description)
DrmPipeline(DrmConnector *conn)
DrmCrtc * crtc() const
void setRenderOrientation(DrmPlane::Transformations orientation)
void setPresentationMode(PresentationMode mode)
std::shared_ptr< DrmConnectorMode > mode() const
void setIccProfile(const std::shared_ptr< IccProfile > &profile)
DrmPipelineLayer * primaryLayer() const
void setOverscan(uint32_t overscan)
DrmConnector::DrmContentType contentType() const
void setGammaRamp(const std::shared_ptr< ColorTransformation > &transformation)
QMap< uint32_t, QList< uint64_t > > formats() const
double desiredMinLuminance() const
Definition edid.cpp:247
std::optional< double > desiredMaxFrameAverageLuminance() const
Definition edid.cpp:252
std::optional< Colorimetry > colorimetry() const
Definition edid.cpp:242
bool supportsPQ() const
Definition edid.cpp:262
Session::Type type
Definition session.cpp:17
PresentationMode
Definition globals.h:276
NamedTransferFunction
Definition colorspace.h:90