29#include <drm_fourcc.h>
32using namespace std::literals;
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}};
43 , m_commitThread(std::make_unique<
DrmCommitThread>(conn->gpu(), conn->connectorName()))
47 m_output->frameFailed();
52DrmPipeline::~DrmPipeline()
54 if (pageflipsPending()) {
59bool DrmPipeline::testScanout()
61 if (gpu()->needsModeset()) {
64 if (gpu()->atomicModeSetting()) {
65 return commitPipelines({
this}, CommitMode::Test) == Error::None;
67 if (m_primaryLayer->currentBuffer()->buffer()->size() != m_pending.mode->size()) {
73 return presentLegacy() == Error::None;
79 Q_ASSERT(m_pending.crtc);
80 if (gpu()->atomicModeSetting()) {
82 auto fullState = std::make_unique<DrmAtomicCommit>(QList<DrmPipeline *>{
this});
83 if (
Error err = prepareAtomicCommit(fullState.get(), CommitMode::Test); err != Error::None) {
86 if (!fullState->test()) {
87 return errnoToError();
90 auto primaryPlaneUpdate = std::make_unique<DrmAtomicCommit>(QList<DrmPipeline *>{
this});
91 if (
Error err = prepareAtomicPresentation(primaryPlaneUpdate.get()); err != Error::None) {
94 if (m_pending.needsModesetProperties && !prepareAtomicModeset(primaryPlaneUpdate.get())) {
95 return Error::InvalidArguments;
97 m_next.needsModesetProperties = m_pending.needsModesetProperties =
false;
98 m_commitThread->addCommit(std::move(primaryPlaneUpdate));
101 if (m_primaryLayer->hasDirectScanoutBuffer()) {
105 return presentLegacy();
109bool DrmPipeline::maybeModeset()
111 m_modesetPresentPending =
true;
112 return gpu()->maybeModeset();
117 Q_ASSERT(!pipelines.isEmpty());
118 if (pipelines[0]->gpu()->atomicModeSetting()) {
119 return commitPipelinesAtomic(pipelines, mode, unusedObjects);
121 return commitPipelinesLegacy(pipelines, mode, unusedObjects);
125DrmPipeline::Error DrmPipeline::commitPipelinesAtomic(
const QList<DrmPipeline *> &pipelines, CommitMode mode,
const QList<DrmObject *> &unusedObjects)
127 auto commit = std::make_unique<DrmAtomicCommit>(pipelines);
128 if (mode == CommitMode::Test) {
131 const bool wantsModeset = std::any_of(pipelines.begin(), pipelines.end(), [](
DrmPipeline *pipeline) {
132 return pipeline->needsModeset();
135 mode = CommitMode::TestAllowModeset;
138 for (
const auto &pipeline : pipelines) {
139 if (Error err = pipeline->prepareAtomicCommit(commit.get(), mode); err != Error::None) {
143 for (
const auto &unused : unusedObjects) {
144 unused->disable(commit.get());
147 case CommitMode::TestAllowModeset: {
148 if (!commit->testAllowModeset()) {
149 qCDebug(KWIN_DRM) <<
"Atomic modeset test failed!" << strerror(errno);
150 return errnoToError();
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();
156 for (
const auto &pipeline : pipelines) {
157 pipeline->m_pending.needsModeset = !withoutModeset;
158 pipeline->m_pending.needsModesetProperties =
true;
162 case CommitMode::CommitModeset: {
167 if (!commit->commitModeset()) {
168 qCCritical(KWIN_DRM) <<
"Atomic modeset commit failed!" << strerror(errno);
169 return errnoToError();
171 for (
const auto pipeline : pipelines) {
172 pipeline->m_next.needsModeset = pipeline->m_pending.needsModeset =
false;
174 commit->pageFlipped(std::chrono::steady_clock::now().time_since_epoch());
177 case CommitMode::Test: {
178 if (!commit->test()) {
179 qCDebug(KWIN_DRM) <<
"Atomic test failed!" << strerror(errno);
180 return errnoToError();
189DrmPipeline::Error DrmPipeline::prepareAtomicCommit(DrmAtomicCommit *commit, CommitMode mode)
191 if (activePending()) {
192 if (Error err = prepareAtomicPresentation(commit); err != Error::None) {
195 if (m_pending.crtc->cursorPlane()) {
196 prepareAtomicCursor(commit);
198 if (mode == CommitMode::TestAllowModeset || mode == CommitMode::CommitModeset || m_pending.needsModesetProperties) {
199 if (!prepareAtomicModeset(commit)) {
200 return Error::InvalidArguments;
204 prepareAtomicDisable(commit);
209static QRect centerBuffer(
const QSize &bufferSize,
const QSize &modeSize)
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);
218 const QSize size = bufferSize / heightScale;
219 const uint32_t xOffset = (modeSize.width() - size.width()) / 2;
220 return QRect(QPoint(xOffset, 0), size);
224DrmPipeline::Error DrmPipeline::prepareAtomicPresentation(DrmAtomicCommit *commit)
226 commit->setPresentationMode(m_pending.presentationMode);
227 if (m_connector->contentType.isValid()) {
228 commit->addEnum(m_connector->contentType, m_pending.contentType);
231 if (m_pending.crtc->vrrEnabled.isValid()) {
232 commit->setVrr(m_pending.crtc, m_pending.presentationMode == PresentationMode::AdaptiveSync || m_pending.presentationMode == PresentationMode::AdaptiveAsync);
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;
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;
245 if (!m_primaryLayer->checkTestBuffer()) {
246 qCWarning(KWIN_DRM) <<
"Checking test buffer failed!";
247 return Error::TestBufferFailed;
249 const auto fb = m_primaryLayer->currentBuffer();
251 return Error::InvalidArguments;
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()) {
259 return Error::InvalidArguments;
261 commit->addEnum(primary->colorEncoding, DrmPlane::ColorEncoding::BT709_YCbCr);
262 commit->addEnum(primary->colorRange, DrmPlane::ColorRange::Limited_YCbCr);
267static const bool s_vrrCursorWorkaround = qEnvironmentVariableIntValue(
"KWIN_DRM_NO_VRR_CURSOR_WORKAROUND") == 0;
269void DrmPipeline::prepareAtomicCursor(DrmAtomicCommit *commit)
271 if (s_vrrCursorWorkaround && m_pending.presentationMode != PresentationMode::VSync) {
273 commit->addProperty(m_pending.crtc->primaryPlane()->srcX, 0);
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()));
286void DrmPipeline::prepareAtomicDisable(DrmAtomicCommit *commit)
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);
298bool DrmPipeline::prepareAtomicModeset(DrmAtomicCommit *commit)
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));
304 if (m_connector->linkStatus.isValid()) {
305 commit->addEnum(m_connector->linkStatus, DrmConnector::LinkStatus::Good);
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);
315 if (m_connector->maxBpc.isValid()) {
316 uint64_t preferred = 8;
317 if (
auto backend =
dynamic_cast<EglGbmBackend *
>(gpu()->platform()->renderBackend()); backend && backend->prefer10bpc()) {
320 commit->addProperty(m_connector->maxBpc, preferred);
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) {
327 if (m_pending.colorDescription.colorimetry() == NamedColorimetry::BT2020) {
328 if (!m_connector->colorspace.isValid() || !m_connector->colorspace.hasEnum(DrmConnector::Colorspace::BT2020_RGB)) {
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);
335 if (m_connector->scalingMode.isValid() && m_connector->scalingMode.hasEnum(DrmConnector::ScalingMode::None)) {
336 commit->addEnum(m_connector->scalingMode, DrmConnector::ScalingMode::None);
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);
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});
350 if (primary->alpha.isValid()) {
351 commit->addProperty(primary->alpha, 0xFFFF);
353 if (primary->pixelBlendMode.isValid()) {
354 commit->addEnum(primary->pixelBlendMode, DrmPlane::PixelBlendMode::PreMultiplied);
356 if (
const auto cursor = m_pending.crtc->cursorPlane()) {
357 if (cursor->rotation.isValid()) {
358 commit->addEnum(cursor->rotation, DrmPlane::Transformations(DrmPlane::Transformation::Rotate0));
360 if (cursor->alpha.isValid()) {
361 commit->addProperty(cursor->alpha, 0xFFFF);
363 if (cursor->pixelBlendMode.isValid()) {
364 commit->addEnum(cursor->pixelBlendMode, DrmPlane::PixelBlendMode::PreMultiplied);
366 prepareAtomicCursor(commit);
371uint32_t DrmPipeline::calculateUnderscan()
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;
379 m_pending.overscan = 128 / aspectRatio;
384DrmPipeline::Error DrmPipeline::errnoToError()
388 return Error::InvalidArguments;
390 return Error::FramePending;
392 return Error::OutofMemory;
394 return Error::NoPermission;
396 return Error::Unknown;
400bool DrmPipeline::updateCursor()
402 if (needsModeset() || !m_pending.crtc || !m_pending.active) {
406 if (m_pending.crtc->cursorPlane()) {
408 auto fullState = std::make_unique<DrmAtomicCommit>(QList<DrmPipeline *>{
this});
409 if (prepareAtomicPresentation(fullState.get()) != Error::None) {
412 prepareAtomicCursor(fullState.get());
413 if (!fullState->test()) {
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));
423 return setCursorLegacy();
427void DrmPipeline::applyPendingChanges()
430 m_commitThread->setModeInfo(m_pending.mode->refreshRate(), m_pending.mode->vblankTime());
432 m_output->renderLoop()->setPresentationSafetyMargin(m_commitThread->safetyMargin());
443 return m_connector->gpu();
448 m_commitThread->pageFlipped(timestamp);
449 if (
type == PageflipType::Modeset && !activePending()) {
453 if (
type == PageflipType::Normal ||
type == PageflipType::Modeset) {
454 m_output->pageFlipped(timestamp, mode);
456 RenderLoopPrivate::get(m_output->renderLoop())->notifyVblank(timestamp);
471QMap<uint32_t, QList<uint64_t>> DrmPipeline::formats()
const
473 return m_pending.formats;
476QMap<uint32_t, QList<uint64_t>> DrmPipeline::cursorFormats()
const
478 if (m_pending.crtc && m_pending.crtc->cursorPlane()) {
479 return m_pending.crtc->cursorPlane()->formats();
481 return legacyCursorFormats;
485bool DrmPipeline::hasCTM()
const
487 return m_pending.crtc && m_pending.crtc->ctm.isValid();
490bool DrmPipeline::hasGammaRamp()
const
492 if (gpu()->atomicModeSetting()) {
493 return m_pending.crtc && m_pending.crtc->gammaLut.isValid();
495 return m_pending.crtc && m_pending.crtc->gammaRampSize() > 0;
499bool DrmPipeline::pruneModifier()
501 const DmaBufAttributes *dmabufAttributes = m_primaryLayer->currentBuffer() ? m_primaryLayer->currentBuffer()->buffer()->dmabufAttributes() :
nullptr;
502 if (!dmabufAttributes) {
505 auto &modifiers = m_pending.formats[dmabufAttributes->
format];
506 if (modifiers == implicitModifier) {
509 modifiers = implicitModifier;
514bool DrmPipeline::needsModeset()
const
516 return m_pending.needsModeset;
519bool DrmPipeline::activePending()
const
521 return m_pending.crtc && m_pending.mode && m_pending.active;
524void DrmPipeline::revertPendingChanges()
529bool DrmPipeline::pageflipsPending()
const
531 return m_commitThread->pageflipsPending();
534bool DrmPipeline::modesetPresentPending()
const
536 return m_modesetPresentPending;
539void DrmPipeline::resetModesetPresentPending()
541 m_modesetPresentPending =
false;
544DrmGammaRamp::DrmGammaRamp(
DrmCrtc *crtc,
const std::shared_ptr<ColorTransformation> &transformation)
545 : m_lut(transformation, crtc->gammaRampSize())
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];
554 m_blob =
DrmBlob::create(crtc->
gpu(), atomicLut.data(),
sizeof(drm_color_lut) * atomicLut.size());
570 return m_pending.crtc;
575 return m_pending.mode;
580 return m_pending.active;
585 return m_pending.enabled;
590 return m_primaryLayer.get();
595 return m_cursorLayer.get();
600 return m_pending.renderOrientation;
605 return m_pending.presentationMode;
610 return m_pending.overscan;
615 return m_pending.rgbRange;
620 return m_pending.contentType;
625 return m_pending.colorDescription;
630 return m_pending.iccProfile;
636 m_pending.gamma = std::make_shared<DrmGammaRamp>(
crtc, m_pending.colorTransformation);
638 m_pending.crtc =
crtc;
642 m_pending.formats = {};
648 m_pending.mode =
mode;
653 m_pending.active =
active;
658 m_pending.enabled = enable;
661void DrmPipeline::setLayers(
const std::shared_ptr<DrmPipelineLayer> &primaryLayer,
const std::shared_ptr<DrmPipelineLayer> &cursorLayer)
669 m_pending.renderOrientation = orientation;
674 m_pending.presentationMode =
mode;
684 m_pending.rgbRange = range;
689 m_pending.colorTransformation = transformation;
690 if (transformation) {
691 m_pending.gamma = std::make_shared<DrmGammaRamp>(m_pending.crtc, transformation);
693 m_pending.gamma.reset();
697static uint64_t doubleToFixed(
double value)
700 uint64_t ret = std::abs(value) * (1ull << 32);
709 if (ctm.isIdentity()) {
710 m_pending.ctm.reset();
712 drm_color_ctm blob = {
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))},
724 m_pending.colorDescription = description;
729 m_pending.contentType =
type;
734 if (m_pending.iccProfile != profile) {
735 m_pending.iccProfile = profile;
749 const auto to16Bit = [](
float value) {
750 return uint16_t(std::round(value / 0.00002));
752 hdr_output_metadata data{
754 .hdmi_metadata_type1 = hdr_metadata_infoframe{
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())},
770 .white_point = {to16Bit(colorimetry.white().x()), to16Bit(colorimetry.white().y())},
785 return m_commitThread->safetyMargin();
static const Colorimetry & fromName(NamedColorimetry name)
static std::shared_ptr< DrmBlob > create(DrmGpu *gpu, const void *data, uint32_t dataSize)
const Edid * edid() const
DrmPlane * primaryPlane() const
int gammaRampSize() const
std::shared_ptr< DrmBlob > blob() const
const ColorLUT & lut() const
bool atomicModeSetting() 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
void setLayers(const std::shared_ptr< DrmPipelineLayer > &primaryLayer, const std::shared_ptr< DrmPipelineLayer > &cursorLayer)
void setRgbRange(Output::RgbRange range)
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)
DrmPlane::Transformations renderOrientation() const
void setColorDescription(const ColorDescription &description)
DrmPipeline(DrmConnector *conn)
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
std::optional< double > desiredMaxFrameAverageLuminance() const
std::optional< Colorimetry > colorimetry() const