From 5a22deda3b738d9e203cdd72c3173a41d6932f33 Mon Sep 17 00:00:00 2001 From: Xaver Hugl Date: Sat, 27 Mar 2021 15:01:34 +0100 Subject: [PATCH] platforms/drm: Introduce DrmPipeline DrmPipeline is what now contains all the drm bits related to modesetting and presentation, instead of that being in DrmOutput. This gives a lot more freedom for managing drm resources and enables far better usage of the atomic API with guaranteed immutability for failed tests. --- src/plugins/platforms/drm/CMakeLists.txt | 1 + src/plugins/platforms/drm/drm_backend.cpp | 26 +- src/plugins/platforms/drm/drm_gpu.cpp | 22 +- src/plugins/platforms/drm/drm_gpu.h | 2 + .../platforms/drm/drm_object_connector.cpp | 81 ++- .../platforms/drm/drm_object_connector.h | 29 +- src/plugins/platforms/drm/drm_object_crtc.cpp | 42 +- src/plugins/platforms/drm/drm_object_crtc.h | 21 +- .../platforms/drm/drm_object_plane.cpp | 26 + src/plugins/platforms/drm/drm_object_plane.h | 3 + src/plugins/platforms/drm/drm_output.cpp | 681 ++++-------------- src/plugins/platforms/drm/drm_output.h | 79 +- src/plugins/platforms/drm/drm_pipeline.cpp | 337 +++++++++ src/plugins/platforms/drm/drm_pipeline.h | 94 +++ 14 files changed, 766 insertions(+), 678 deletions(-) create mode 100644 src/plugins/platforms/drm/drm_pipeline.cpp create mode 100644 src/plugins/platforms/drm/drm_pipeline.h diff --git a/src/plugins/platforms/drm/CMakeLists.txt b/src/plugins/platforms/drm/CMakeLists.txt index b10ae4b551..363185ee42 100644 --- a/src/plugins/platforms/drm/CMakeLists.txt +++ b/src/plugins/platforms/drm/CMakeLists.txt @@ -13,6 +13,7 @@ set(DRM_SOURCES drm_gpu.cpp egl_multi_backend.cpp abstract_egl_drm_backend.cpp + drm_pipeline.cpp ) if (HAVE_GBM) diff --git a/src/plugins/platforms/drm/drm_backend.cpp b/src/plugins/platforms/drm/drm_backend.cpp index 5622cbdf53..9e2d25de4f 100644 --- a/src/plugins/platforms/drm/drm_backend.cpp +++ b/src/plugins/platforms/drm/drm_backend.cpp @@ -155,11 +155,13 @@ void DrmBackend::reactivate() if (!usesSoftwareCursor()) { for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { DrmOutput *o = *it; - // only relevant in atomic mode - o->m_modesetRequested = true; - o->m_crtc->blank(o); - o->showCursor(); - o->moveCursor(); + if (o->isEnabled()) { + o->updateMode(o->connector()->currentModeIndex()); + o->showCursor(); + o->moveCursor(); + } else { + o->updateEnablement(false); + } } } @@ -178,7 +180,6 @@ void DrmBackend::deactivate() } for (DrmOutput *output : qAsConst(m_outputs)) { - output->hideCursor(); output->renderLoop()->inhibit(); } @@ -431,15 +432,16 @@ QString DrmBackend::generateOutputConfigurationUuid() const void DrmBackend::enableOutput(DrmOutput *output, bool enable) { + bool enabled = m_enabledOutputs.contains(output); + if (enabled == enable) { + return; + } if (enable) { - Q_ASSERT(!m_enabledOutputs.contains(output)); m_enabledOutputs << output; emit output->gpu()->outputEnabled(output); emit outputEnabled(output); } else { - Q_ASSERT(m_enabledOutputs.contains(output)); m_enabledOutputs.removeOne(output); - Q_ASSERT(!m_enabledOutputs.contains(output)); emit output->gpu()->outputDisabled(output); emit outputDisabled(output); } @@ -520,7 +522,11 @@ void DrmBackend::updateCursor() qCDebug(KWIN_DRM) << "Failed to show cursor on output" << output->name(); break; } - output->moveCursor(); + success = output->moveCursor(); + if (!success) { + qCDebug(KWIN_DRM) << "Failed to move cursor on output" << output->name(); + break; + } } setSoftwareCursor(!success); diff --git a/src/plugins/platforms/drm/drm_gpu.cpp b/src/plugins/platforms/drm/drm_gpu.cpp index c3b8b57ae4..c43eb97ee2 100644 --- a/src/plugins/platforms/drm/drm_gpu.cpp +++ b/src/plugins/platforms/drm/drm_gpu.cpp @@ -259,12 +259,10 @@ bool DrmGpu::updateOutputs() DrmOutput *output = new DrmOutput(this->m_backend, this); output->m_conn = con; output->m_crtc = crtc; - output->m_mode = connector->modes[0]; output->m_primaryPlane = getCompatiblePlane(DrmPlane::TypeIndex::Primary, crtc); - output->m_cursorPlane = getCompatiblePlane(DrmPlane::TypeIndex::Cursor, crtc); - qCDebug(KWIN_DRM) << "For new output use mode " << output->m_mode.name << output->m_mode.hdisplay << output->m_mode.vdisplay; - if (!output->init(connector.data())) { + qCDebug(KWIN_DRM) << "For new output use mode" << con->currentMode().mode.name; + if (!output->init()) { qCWarning(KWIN_DRM) << "Failed to create output for connector " << con->id(); delete output; continue; @@ -413,4 +411,20 @@ void DrmGpu::dispatchEvents() drmHandleEvent(m_fd, &context); } +QSharedPointer DrmGpu::createTestbuffer(const QSize &size) +{ +#if HAVE_GBM + if (m_gbmDevice) { + gbm_bo *bo = gbm_bo_create(m_gbmDevice, size.width(), size.height(), GBM_FORMAT_XRGB8888, GBM_BO_USE_SCANOUT); + if (bo) { + auto buffer = QSharedPointer::create(this, bo, nullptr); + if (buffer->bufferId()) { + return buffer; + } + } + } +#endif + return QSharedPointer::create(this, size); +} + } diff --git a/src/plugins/platforms/drm/drm_gpu.h b/src/plugins/platforms/drm/drm_gpu.h index cd6eec2cae..9931a89af1 100644 --- a/src/plugins/platforms/drm/drm_gpu.h +++ b/src/plugins/platforms/drm/drm_gpu.h @@ -98,6 +98,8 @@ public: void waitIdle(); + QSharedPointer createTestbuffer(const QSize &size); + Q_SIGNALS: void outputAdded(DrmOutput *output); void outputRemoved(DrmOutput *output); diff --git a/src/plugins/platforms/drm/drm_object_connector.cpp b/src/plugins/platforms/drm/drm_object_connector.cpp index 47a10a0685..16623d3f49 100644 --- a/src/plugins/platforms/drm/drm_object_connector.cpp +++ b/src/plugins/platforms/drm/drm_object_connector.cpp @@ -3,6 +3,7 @@ This file is part of the KDE project. SPDX-FileCopyrightText: 2016 Roman Gilg + SPDX-FileCopyrightText: 2021 Xaver Hugl SPDX-License-Identifier: GPL-2.0-or-later */ @@ -29,8 +30,31 @@ DrmConnector::DrmConnector(DrmGpu *gpu, uint32_t connectorId) DrmConnector::~DrmConnector() = default; +namespace { +quint64 refreshRateForMode(_drmModeModeInfo *m) +{ + // Calculate higher precision (mHz) refresh rate + // logic based on Weston, see compositor-drm.c + quint64 refreshRate = (m->clock * 1000000LL / m->htotal + m->vtotal / 2) / m->vtotal; + if (m->flags & DRM_MODE_FLAG_INTERLACE) { + refreshRate *= 2; + } + if (m->flags & DRM_MODE_FLAG_DBLSCAN) { + refreshRate /= 2; + } + if (m->vscan > 1) { + refreshRate /= m->vscan; + } + return refreshRate; +} +} + bool DrmConnector::init() { + if (!m_conn->count_modes) { + return false; + } + qCDebug(KWIN_DRM) << "Creating connector" << id(); if (!initProps({ @@ -72,16 +96,26 @@ bool DrmConnector::init() m_physicalSize = overwriteSize; } + // init modes + for (int i = 0; i < m_conn->count_modes; i++) { + auto mode = m_conn->modes[i]; + Mode m; + m.mode = mode; + m.size = QSize(mode.hdisplay, mode.vdisplay); + m.refreshRate = refreshRateForMode(&mode); + m_modes << m; + } + return true; } bool DrmConnector::isConnected() { - DrmScopedPointer con(drmModeGetConnector(gpu()->fd(), id())); - if (!con) { + m_conn.reset(drmModeGetConnector(gpu()->fd(), id())); + if (!m_conn) { return false; } - return con->connection == DRM_MODE_CONNECTED; + return m_conn->connection == DRM_MODE_CONNECTED; } static QHash s_connectorNames = { @@ -132,5 +166,46 @@ QSize DrmConnector::physicalSize() const return m_physicalSize; } +const DrmConnector::Mode &DrmConnector::currentMode() const +{ + return m_modes[m_modeIndex]; +} + +int DrmConnector::currentModeIndex() const +{ + return m_modeIndex; +} + +const QVector &DrmConnector::modes() +{ + return m_modes; +} + +void DrmConnector::setModeIndex(int index) +{ + m_modeIndex = index; +} + +AbstractWaylandOutput::SubPixel DrmConnector::subpixel() const +{ + switch (m_conn->subpixel) { + case DRM_MODE_SUBPIXEL_UNKNOWN: + return AbstractWaylandOutput::SubPixel::Unknown; + case DRM_MODE_SUBPIXEL_NONE: + return AbstractWaylandOutput::SubPixel::None; + case DRM_MODE_SUBPIXEL_HORIZONTAL_RGB: + return AbstractWaylandOutput::SubPixel::Horizontal_RGB; + case DRM_MODE_SUBPIXEL_HORIZONTAL_BGR: + return AbstractWaylandOutput::SubPixel::Horizontal_BGR; + case DRM_MODE_SUBPIXEL_VERTICAL_RGB: + return AbstractWaylandOutput::SubPixel::Vertical_RGB; + case DRM_MODE_SUBPIXEL_VERTICAL_BGR: + return AbstractWaylandOutput::SubPixel::Vertical_BGR; + default: + Q_UNREACHABLE(); + } +} + + } diff --git a/src/plugins/platforms/drm/drm_object_connector.h b/src/plugins/platforms/drm/drm_object_connector.h index 4dbec891a6..a69ed34611 100644 --- a/src/plugins/platforms/drm/drm_object_connector.h +++ b/src/plugins/platforms/drm/drm_object_connector.h @@ -3,16 +3,21 @@ This file is part of the KDE project. SPDX-FileCopyrightText: 2016 Roman Gilg + SPDX-FileCopyrightText: 2021 Xaver Hugl SPDX-License-Identifier: GPL-2.0-or-later */ -#ifndef KWIN_DRM_OBJECT_CONNECTOR_H -#define KWIN_DRM_OBJECT_CONNECTOR_H +#pragma once + +#include +#include #include #include "drm_object.h" #include "edid.h" +#include "drm_pointer.h" +#include "abstract_wayland_output.h" namespace KWin { @@ -61,15 +66,31 @@ public: bool isInternal() const; QSize physicalSize() const; + struct Mode { + drmModeModeInfo mode; + QSize size; + uint32_t refreshRate; + }; + /** + * until the actual current mode is set with setMode(int) + * this will always return the first mode + */ + const Mode ¤tMode() const; + int currentModeIndex() const; + const QVector &modes(); + void setModeIndex(int index); + + AbstractWaylandOutput::SubPixel subpixel() const; + private: DrmScopedPointer m_conn; QVector m_encoders; Edid m_edid; QSize m_physicalSize = QSize(-1, -1); + QVector m_modes; + int m_modeIndex = 0; }; } -#endif - diff --git a/src/plugins/platforms/drm/drm_object_crtc.cpp b/src/plugins/platforms/drm/drm_object_crtc.cpp index 745ff2c5ce..a6c9627608 100644 --- a/src/plugins/platforms/drm/drm_object_crtc.cpp +++ b/src/plugins/platforms/drm/drm_object_crtc.cpp @@ -3,6 +3,7 @@ This file is part of the KDE project. SPDX-FileCopyrightText: 2016 Roman Gilg + SPDX-FileCopyrightText: 2021 Xaver Hugl SPDX-License-Identifier: GPL-2.0-or-later */ @@ -33,6 +34,8 @@ bool DrmCrtc::init() return initProps({ PropertyDefinition(QByteArrayLiteral("MODE_ID")), PropertyDefinition(QByteArrayLiteral("ACTIVE")), + PropertyDefinition(QByteArrayLiteral("GAMMA_LUT")), + PropertyDefinition(QByteArrayLiteral("GAMMA_LUT_SIZE")), }, DRM_MODE_OBJECT_CRTC); } @@ -40,45 +43,12 @@ void DrmCrtc::flipBuffer() { m_currentBuffer = m_nextBuffer; m_nextBuffer = nullptr; - - delete m_blackBuffer; - m_blackBuffer = nullptr; } -bool DrmCrtc::blank(DrmOutput *output) +drmModeModeInfo DrmCrtc::queryCurrentMode() { - if (gpu()->atomicModeSetting()) { - return false; - } - - if (!m_blackBuffer) { - DrmDumbBuffer *blackBuffer = new DrmDumbBuffer(gpu(), output->pixelSize()); - if (!blackBuffer->map()) { - delete blackBuffer; - return false; - } - blackBuffer->image()->fill(Qt::black); - m_blackBuffer = blackBuffer; - } - - if (output->setModeLegacy(m_blackBuffer)) { - m_currentBuffer = nullptr; - m_nextBuffer = nullptr; - return true; - } - return false; -} - -bool DrmCrtc::setGammaRamp(const GammaRamp &gamma) -{ - uint16_t *red = const_cast(gamma.red()); - uint16_t *green = const_cast(gamma.green()); - uint16_t *blue = const_cast(gamma.blue()); - - const bool isError = drmModeCrtcSetGamma(gpu()->fd(), id(), - gamma.size(), red, green, blue); - - return !isError; + m_crtc.reset(drmModeGetCrtc(gpu()->fd(), id())); + return m_crtc->mode; } } diff --git a/src/plugins/platforms/drm/drm_object_crtc.h b/src/plugins/platforms/drm/drm_object_crtc.h index 1ee1ca94d9..64af3ef962 100644 --- a/src/plugins/platforms/drm/drm_object_crtc.h +++ b/src/plugins/platforms/drm/drm_object_crtc.h @@ -13,6 +13,8 @@ #include +#include "drm_pointer.h" + namespace KWin { @@ -31,7 +33,9 @@ public: enum class PropertyIndex : uint32_t { ModeId = 0, - Active, + Active = 1, + Gamma_LUT = 2, + Gamma_LUT_size = 3, Count }; @@ -53,19 +57,26 @@ public: } void flipBuffer(); - bool blank(DrmOutput *output); int gammaRampSize() const { + if (const auto &prop = m_props.at(static_cast(PropertyIndex::Gamma_LUT_size))) { + return prop->value(); + } return m_crtc->gamma_size; } - bool setGammaRamp(const GammaRamp &gamma); + + bool hasGammaProp() const { + return m_props.at(static_cast(PropertyIndex::Gamma_LUT)); + } + + drmModeModeInfo queryCurrentMode(); private: DrmScopedPointer m_crtc; + int m_pipeIndex; + QSharedPointer m_currentBuffer; QSharedPointer m_nextBuffer; - DrmDumbBuffer *m_blackBuffer = nullptr; - int m_pipeIndex; }; } diff --git a/src/plugins/platforms/drm/drm_object_plane.cpp b/src/plugins/platforms/drm/drm_object_plane.cpp index 7916c9e53d..19e9391c29 100644 --- a/src/plugins/platforms/drm/drm_object_plane.cpp +++ b/src/plugins/platforms/drm/drm_object_plane.cpp @@ -118,4 +118,30 @@ void DrmPlane::flipBuffer() m_next = nullptr; } +void DrmPlane::setScaled(const QSize &srcSize, const QSize &modeSize, int crtcId, bool enable) +{ + QPoint targetPos = QPoint(0, 0); + QSize targetSize = modeSize; + if (modeSize != srcSize) { + targetSize = srcSize.scaled(modeSize, Qt::AspectRatioMode::KeepAspectRatio); + targetPos.setX((modeSize.width() - targetSize.width()) / 2); + targetPos.setY((modeSize.height() - targetSize.height()) / 2); + } + set(srcSize, targetPos, targetSize, crtcId, enable); +} + +void DrmPlane::set(const QSize &src, const QPoint &dstPos, const QSize &dstSize, int crtcId, bool enable) +{ + setValue(PropertyIndex::SrcX, 0); + setValue(PropertyIndex::SrcY, 0); + setValue(PropertyIndex::SrcW, src.width() << 16); + setValue(PropertyIndex::SrcH, src.height() << 16); + setValue(PropertyIndex::CrtcX, enable ? dstPos.x() : 0); + setValue(PropertyIndex::CrtcY, enable ? dstPos.y() : 0); + setValue(PropertyIndex::CrtcW, dstSize.width()); + setValue(PropertyIndex::CrtcH, dstSize.height()); + setValue(PropertyIndex::CrtcId, enable ? crtcId : 0); + setValue(PropertyIndex::FbId, (m_next && enable) ? m_next->bufferId() : 0); +} + } diff --git a/src/plugins/platforms/drm/drm_object_plane.h b/src/plugins/platforms/drm/drm_object_plane.h index 1080afb2dc..4ec02301d8 100644 --- a/src/plugins/platforms/drm/drm_object_plane.h +++ b/src/plugins/platforms/drm/drm_object_plane.h @@ -90,6 +90,9 @@ public: return m_supportedTransformations; } + void setScaled(const QSize &srcSize, const QSize &modeSize, int crtcId, bool enable); + void set(const QSize &src, const QPoint &dstPos, const QSize &dstSize, int crtcId, bool enable); + private: QSharedPointer m_current; QSharedPointer m_next; diff --git a/src/plugins/platforms/drm/drm_output.cpp b/src/plugins/platforms/drm/drm_output.cpp index c01816b201..ce8bdf1d66 100644 --- a/src/plugins/platforms/drm/drm_output.cpp +++ b/src/plugins/platforms/drm/drm_output.cpp @@ -3,6 +3,7 @@ This file is part of the KDE project. SPDX-FileCopyrightText: 2015 Martin Gräßlin + SPDX-FileCopyrightText: 2021 Xaver Hugl SPDX-License-Identifier: GPL-2.0-or-later */ @@ -29,6 +30,7 @@ #include #include "drm_gpu.h" +#include "drm_pipeline.h" namespace KWin { @@ -45,6 +47,7 @@ DrmOutput::~DrmOutput() { Q_ASSERT(!m_pageFlipPending); teardown(); + delete m_pipeline; } RenderLoop *DrmOutput::renderLoop() const @@ -59,15 +62,12 @@ void DrmOutput::teardown() } m_deleted = true; hideCursor(); - m_crtc->blank(this); if (m_primaryPlane) { // TODO: when having multiple planes, also clean up these m_primaryPlane->setCurrent(nullptr); } - m_cursor[0].reset(nullptr); - m_cursor[1].reset(nullptr); if (!m_pageFlipPending) { deleteLater(); } //else will be deleted in the page flip handler @@ -82,35 +82,23 @@ void DrmOutput::releaseBuffers() m_primaryPlane->setNext(nullptr); } -bool DrmOutput::hideCursor() +bool DrmOutput::initCursor(const QSize &cursorSize) { - return drmModeSetCursor(m_gpu->fd(), m_crtc->id(), 0, 0, 0) == 0; + m_cursor = QSharedPointer::create(m_gpu, cursorSize); + if (!m_cursor->map(QImage::Format_ARGB32_Premultiplied)) { + return false; + } + return updateCursor(); } -bool DrmOutput::showCursor(DrmDumbBuffer *c) +bool DrmOutput::hideCursor() { - const QSize &s = c->size(); - return drmModeSetCursor(m_gpu->fd(), m_crtc->id(), c->handle(), s.width(), s.height()) == 0; + return m_pipeline->setCursor(nullptr); } bool DrmOutput::showCursor() { - if (m_deleted) { - return false; - } - - const bool ret = showCursor(m_cursor[m_cursorIndex].data()); - if (!ret) { - qCDebug(KWIN_DRM) << "DrmOutput::showCursor(DrmDumbBuffer) failed"; - return ret; - } - - if (m_hasNewCursor) { - m_cursorIndex = (m_cursorIndex + 1) % 2; - m_hasNewCursor = false; - } - - return ret; + return m_pipeline->setCursor(m_cursor); } static bool isCursorSpriteCompatible(const QImage *buffer, const QImage *sprite) @@ -129,32 +117,31 @@ bool DrmOutput::updateCursor() return false; } const Cursor *cursor = Cursors::self()->currentCursor(); + if (!cursor) { + hideCursor(); + return true; + } const QImage cursorImage = cursor->image(); if (cursorImage.isNull()) { - return false; + hideCursor(); + return true; } - - QImage *c = m_cursor[m_cursorIndex]->image(); + QImage *c = m_cursor->image(); c->setDevicePixelRatio(scale()); - if (!isCursorSpriteCompatible(c, &cursorImage)) { // If the cursor image is too big, fall back to rendering the software cursor. return false; } - - m_hasNewCursor = true; c->fill(Qt::transparent); - QPainter p; p.begin(c); p.setWorldTransform(logicalToNativeMatrix(cursor->rect(), 1, transform()).toTransform()); p.drawImage(QPoint(0, 0), cursorImage); p.end(); - - return true; + return m_pipeline->setCursor(m_cursor); } -void DrmOutput::moveCursor() +bool DrmOutput::moveCursor() { Cursor *cursor = Cursors::self()->currentCursor(); const QMatrix4x4 hotspotMatrix = logicalToNativeMatrix(cursor->rect(), scale(), transform()); @@ -163,89 +150,66 @@ void DrmOutput::moveCursor() QPoint pos = monitorMatrix.map(cursor->pos()); pos -= hotspotMatrix.map(cursor->hotspot()); - drmModeMoveCursor(m_gpu->fd(), m_crtc->id(), pos.x(), pos.y()); + return m_pipeline->moveCursor(pos); } -namespace { -quint64 refreshRateForMode(_drmModeModeInfo *m) -{ - // Calculate higher precision (mHz) refresh rate - // logic based on Weston, see compositor-drm.c - quint64 refreshRate = (m->clock * 1000000LL / m->htotal + m->vtotal / 2) / m->vtotal; - if (m->flags & DRM_MODE_FLAG_INTERLACE) { - refreshRate *= 2; - } - if (m->flags & DRM_MODE_FLAG_DBLSCAN) { - refreshRate /= 2; - } - if (m->vscan > 1) { - refreshRate /= m->vscan; - } - return refreshRate; -} -} - -static AbstractWaylandOutput::SubPixel drmSubPixelToKWinSubPixel(drmModeSubPixel subpixel) -{ - switch (subpixel) { - case DRM_MODE_SUBPIXEL_UNKNOWN: - return AbstractWaylandOutput::SubPixel::Unknown; - case DRM_MODE_SUBPIXEL_NONE: - return AbstractWaylandOutput::SubPixel::None; - case DRM_MODE_SUBPIXEL_HORIZONTAL_RGB: - return AbstractWaylandOutput::SubPixel::Horizontal_RGB; - case DRM_MODE_SUBPIXEL_HORIZONTAL_BGR: - return AbstractWaylandOutput::SubPixel::Horizontal_BGR; - case DRM_MODE_SUBPIXEL_VERTICAL_RGB: - return AbstractWaylandOutput::SubPixel::Vertical_RGB; - case DRM_MODE_SUBPIXEL_VERTICAL_BGR: - return AbstractWaylandOutput::SubPixel::Vertical_BGR; - default: - Q_UNREACHABLE(); - } -} - -bool DrmOutput::init(drmModeConnector *connector) +bool DrmOutput::init() { if (m_gpu->atomicModeSetting() && !m_primaryPlane) { return false; } - setSubPixelInternal(drmSubPixelToKWinSubPixel(connector->subpixel)); + setSubPixelInternal(m_conn->subpixel()); setInternal(m_conn->isInternal()); - setCapabilityInternal(DrmOutput::Capability::Dpms); - initOutputDevice(connector); + setCapabilityInternal(Capability::Dpms); + initOutputDevice(); - if (!m_gpu->atomicModeSetting() && !m_crtc->blank(this)) { - // We use legacy mode and the initial output blank failed. - return false; - } + m_pipeline = new DrmPipeline(this, m_gpu, m_conn, m_crtc, m_primaryPlane, m_cursorPlane); + updateMode(0); + // renderloop will be un-inhibited when updating DPMS + m_renderLoop->inhibit(); + m_dpmsEnabled = false; setDpmsMode(DpmsMode::On); - return true; + return m_dpmsEnabled; } -void DrmOutput::initOutputDevice(drmModeConnector *connector) +static bool checkIfEqual(_drmModeModeInfo one, _drmModeModeInfo two) { + // for some reason a few values are 0 + // thus they're ignored here + return one.clock == two.clock + && one.hdisplay == two.hdisplay + && one.hsync_start == two.hsync_start + && one.hsync_end == two.hsync_end + && one.htotal == two.htotal + && one.hskew == two.hskew + && one.vdisplay == two.vdisplay + && one.vsync_start == two.vsync_start + && one.vsync_end == two.vsync_end + && one.vtotal == two.vtotal + && one.vscan == two.vscan + && one.vrefresh == two.vrefresh; +} + +void DrmOutput::initOutputDevice() { - // read in mode information QVector modes; - modes.reserve(connector->count_modes); - for (int i = 0; i < connector->count_modes; ++i) { - // TODO: in AMS here we could read and store for later every mode's blob_id - // would simplify isCurrentMode(..) and presentAtomically(..) in case of mode set - auto *m = &connector->modes[i]; + auto modelist = m_conn->modes(); + auto currentMode = m_crtc->queryCurrentMode(); + for (int i = 0; i < modelist.count(); i++) { + const auto &m = modelist[i]; Mode mode; - if (isCurrentMode(m)) { + if (checkIfEqual(m.mode, currentMode)) { mode.flags |= ModeFlag::Current; + m_conn->setModeIndex(i); } - if (m->type & DRM_MODE_TYPE_PREFERRED) { + if (m.mode.type & DRM_MODE_TYPE_PREFERRED) { mode.flags |= ModeFlag::Preferred; } - mode.id = i; - mode.size = QSize(m->hdisplay, m->vdisplay); - mode.refreshRate = refreshRateForMode(m); + mode.size = m.size; + mode.refreshRate = m.refreshRate; modes << mode; } @@ -255,178 +219,50 @@ void DrmOutput::initOutputDevice(drmModeConnector *connector) m_conn->physicalSize(), modes, m_conn->edid()->raw()); } -bool DrmOutput::isCurrentMode(const drmModeModeInfo *mode) const -{ - return mode->clock == m_mode.clock - && mode->hdisplay == m_mode.hdisplay - && mode->hsync_start == m_mode.hsync_start - && mode->hsync_end == m_mode.hsync_end - && mode->htotal == m_mode.htotal - && mode->hskew == m_mode.hskew - && mode->vdisplay == m_mode.vdisplay - && mode->vsync_start == m_mode.vsync_start - && mode->vsync_end == m_mode.vsync_end - && mode->vtotal == m_mode.vtotal - && mode->vscan == m_mode.vscan - && mode->vrefresh == m_mode.vrefresh - && mode->flags == m_mode.flags - && mode->type == m_mode.type - && qstrcmp(mode->name, m_mode.name) == 0; -} - -bool DrmOutput::initCursor(const QSize &cursorSize) -{ - auto createCursor = [this, cursorSize] (int index) { - m_cursor[index].reset(new DrmDumbBuffer(m_gpu, cursorSize)); - if (!m_cursor[index]->map(QImage::Format_ARGB32_Premultiplied)) { - return false; - } - return true; - }; - if (!createCursor(0) || !createCursor(1)) { - return false; - } - return true; -} - void DrmOutput::updateEnablement(bool enable) { - if (enable) { - m_dpmsModePending = DpmsMode::On; - if (m_gpu->atomicModeSetting()) { - atomicEnable(); - } else { - if (dpmsLegacyApply()) { - m_backend->enableOutput(this, true); - } - } + if (m_dpmsEnabled == enable) { + m_backend->enableOutput(this, enable); + return; + } + if (m_pipeline->setEnablement(enable)) { + m_backend->enableOutput(this, enable); + if (enable) { + m_renderLoop->inhibit(); + } else { + m_renderLoop->uninhibit(); + } + m_dpmsEnabled = enable; } else { - m_dpmsModePending = DpmsMode::Off; - if (m_gpu->atomicModeSetting()) { - atomicDisable(); - } else { - if (dpmsLegacyApply()) { - m_backend->enableOutput(this, false); - } - } - } -} - -void DrmOutput::atomicEnable() -{ - m_modesetRequested = true; - - if (m_atomicOffPending) { - Q_ASSERT(m_pageFlipPending); - m_atomicOffPending = false; - } - m_backend->enableOutput(this, true); - dpmsFinishOn(); - - if (Compositor *compositor = Compositor::self()) { - compositor->addRepaintFull(); - } -} - -void DrmOutput::atomicDisable() -{ - m_modesetRequested = true; - - m_backend->enableOutput(this, false); - m_atomicOffPending = true; - if (!m_pageFlipPending) { - dpmsAtomicOff(); + qCWarning(KWIN_DRM) << "Setting enablement to" << enable << "failed!" << strerror(errno); } } void DrmOutput::setDpmsMode(DpmsMode mode) { - if (!m_conn->dpms() || !isEnabled()) { + bool newDpmsEnable = mode == DpmsMode::On; + if (newDpmsEnable == m_dpmsEnabled) { + qCDebug(KWIN_DRM) << "New DPMS mode equals old mode. DPMS unchanged."; + AbstractWaylandOutput::setDpmsModeInternal(mode); return; } - - if (mode == m_dpmsModePending) { - return; - } - - m_dpmsModePending = mode; - - if (m_gpu->atomicModeSetting()) { - m_modesetRequested = true; - if (mode == DpmsMode::On) { - if (m_atomicOffPending) { - Q_ASSERT(m_pageFlipPending); - m_atomicOffPending = false; + if (m_pipeline->setEnablement(newDpmsEnable)) { + m_dpmsEnabled = newDpmsEnable; + AbstractWaylandOutput::setDpmsModeInternal(mode); + if (m_dpmsEnabled) { + m_renderLoop->uninhibit(); + if (Compositor *compositor = Compositor::self()) { + compositor->addRepaintFull(); } - dpmsFinishOn(); } else { - m_atomicOffPending = true; - if (!m_pageFlipPending) { - dpmsAtomicOff(); - } + m_renderLoop->inhibit(); } } else { - dpmsLegacyApply(); + qCWarning(KWIN_DRM) << "Setting DPMS failed!" << strerror(errno); } } -void DrmOutput::dpmsFinishOn() -{ - qCDebug(KWIN_DRM) << "DPMS mode set for output" << m_crtc->id() << "to On."; - - m_backend->checkOutputsAreOn(); - m_crtc->blank(this); - m_renderLoop->uninhibit(); - if (Compositor *compositor = Compositor::self()) { - compositor->addRepaintFull(); - } -} - -void DrmOutput::dpmsFinishOff() -{ - qCDebug(KWIN_DRM) << "DPMS mode set for output" << m_crtc->id() << "to Off."; - - if (isEnabled()) { - m_backend->createDpmsFilter(); - } - m_renderLoop->inhibit(); -} - -static uint64_t kwinDpmsModeToDrmDpmsMode(AbstractWaylandOutput::DpmsMode dpmsMode) -{ - switch (dpmsMode) { - case AbstractWaylandOutput::DpmsMode::On: - return DRM_MODE_DPMS_ON; - case AbstractWaylandOutput::DpmsMode::Standby: - return DRM_MODE_DPMS_STANDBY; - case AbstractWaylandOutput::DpmsMode::Suspend: - return DRM_MODE_DPMS_SUSPEND; - case AbstractWaylandOutput::DpmsMode::Off: - return DRM_MODE_DPMS_OFF; - default: - Q_UNREACHABLE(); - } -} - -bool DrmOutput::dpmsLegacyApply() -{ - if (drmModeConnectorSetProperty(m_gpu->fd(), m_conn->id(), - m_conn->dpms()->propId(), - kwinDpmsModeToDrmDpmsMode(m_dpmsModePending)) < 0) { - m_dpmsModePending = dpmsMode(); - qCWarning(KWIN_DRM) << "Setting DPMS failed"; - return false; - } - if (m_dpmsModePending == DpmsMode::On) { - dpmsFinishOn(); - } else { - dpmsFinishOff(); - } - setDpmsModeInternal(m_dpmsModePending); - return true; -} - DrmPlane::Transformations outputToPlaneTransform(DrmOutput::Transform transform) { using OutTrans = DrmOutput::Transform; @@ -472,7 +308,7 @@ void DrmOutput::updateTransform(Transform transform) || transform == Transform::Flipped90 || transform == Transform::Rotated270 || transform == Transform::Flipped270; - + const auto ¤tTransform = m_primaryPlane->transformation(); if (!qEnvironmentVariableIsSet("KWIN_DRM_SW_ROTATIONS_ONLY") && (m_primaryPlane->supportedTransformations() & planeTransform) && !isPortrait) { @@ -480,27 +316,28 @@ void DrmOutput::updateTransform(Transform transform) } else { m_primaryPlane->setTransformation(DrmPlane::Transformation::Rotate0); } + if (!m_pipeline->test()) { + m_primaryPlane->setTransformation(currentTransform); + } } - m_modesetRequested = true; // show cursor only if is enabled, i.e if pointer device is presentP if (!m_backend->isCursorHidden() && !m_backend->usesSoftwareCursor()) { // the cursor might need to get rotated - updateCursor(); showCursor(); + updateCursor(); } } void DrmOutput::updateMode(uint32_t width, uint32_t height, uint32_t refreshRate) { - if (m_mode.hdisplay == width && m_mode.vdisplay == height && m_mode.vrefresh == refreshRate) { + if (m_conn->currentMode().size == QSize(width, height) && m_conn->currentMode().refreshRate == refreshRate) { return; } // try to find a fitting mode - DrmScopedPointer connector(drmModeGetConnectorCurrent(m_gpu->fd(), m_conn->id())); - for (int i = 0; i < connector->count_modes; i++) { - auto mode = connector->modes[i]; - if (mode.hdisplay == width && mode.vdisplay == height && mode.vrefresh == refreshRate) { + auto modelist = m_conn->modes(); + for (int i = 0; i < modelist.count(); i++) { + if (modelist[i].size == QSize(width, height) && modelist[i].refreshRate == refreshRate) { updateMode(i); return; } @@ -511,339 +348,69 @@ void DrmOutput::updateMode(uint32_t width, uint32_t height, uint32_t refreshRate void DrmOutput::updateMode(int modeIndex) { - // get all modes on the connector - DrmScopedPointer connector(drmModeGetConnector(m_gpu->fd(), m_conn->id())); - if (connector->count_modes <= modeIndex) { - // TODO: error? + const auto &modelist = m_conn->modes(); + if (modeIndex >= modelist.count()) { + qCWarning(KWIN_DRM, "Mode %d for output %s doesn't exist", modeIndex, qPrintable(uuid().toString())); return; } - if (isCurrentMode(&connector->modes[modeIndex])) { - // nothing to do - return; + const auto &mode = modelist[modeIndex]; + QSize srcSize = mode.size; + if (hardwareTransforms() && + (transform() == Transform::Rotated270 + || transform() == Transform::Rotated90 + || transform() == Transform::Flipped90 + || transform() == Transform::Flipped270)) { + srcSize = srcSize.transposed(); } - m_mode = connector->modes[modeIndex]; - m_modesetRequested = true; - setCurrentModeInternal(); -} - -void DrmOutput::setCurrentModeInternal() -{ - AbstractWaylandOutput::setCurrentModeInternal(QSize(m_mode.hdisplay, m_mode.vdisplay), - refreshRateForMode(&m_mode)); + if (m_pipeline->modeset(srcSize, mode.mode)) { + m_conn->setModeIndex(modeIndex); + qCDebug(KWIN_DRM, "Modeset successful on %s (%dx%d@%f)", qPrintable(name()), modeSize().width(), modeSize().height(), refreshRate() * 0.001); + } else { + qCWarning(KWIN_DRM, "Modeset failed on %s (%dx%d@%f)! %s", qPrintable(name()), modeSize().width(), modeSize().height(), refreshRate() * 0.001, strerror(errno)); + } + // this must be done even if it fails, for the initial modeset + const auto ¤tMode = m_conn->currentMode(); + AbstractWaylandOutput::setCurrentModeInternal(currentMode.size, currentMode.refreshRate); + m_renderLoop->setRefreshRate(currentMode.refreshRate); } void DrmOutput::pageFlipped() { - // In legacy mode we might get a page flip through a blank. - Q_ASSERT(m_pageFlipPending || !m_gpu->atomicModeSetting()); m_pageFlipPending = false; - if (m_deleted) { + qCDebug(KWIN_DRM) << "delete-pageflip"; deleteLater(); return; } - - if (!m_crtc) { - return; - } if (m_gpu->atomicModeSetting()) { - for (DrmPlane *p : m_nextPlanesFlipList) { - p->flipBuffer(); + m_primaryPlane->flipBuffer(); + if (m_cursorPlane) { + m_cursorPlane->flipBuffer(); } - m_nextPlanesFlipList.clear(); } else { m_crtc->flipBuffer(); } - - if (m_atomicOffPending) { - dpmsAtomicOff(); - } } bool DrmOutput::present(const QSharedPointer &buffer) -{ - if (!buffer || buffer->bufferId() == 0) { - return false; - } - if (m_dpmsModePending != DpmsMode::On) { - return false; - } - return m_gpu->atomicModeSetting() ? presentAtomically(buffer) : presentLegacy(buffer); -} - -bool DrmOutput::dpmsAtomicOff() -{ - m_atomicOffPending = false; - - // TODO: With multiple planes: deactivate all of them here - m_primaryPlane->setNext(nullptr); - m_nextPlanesFlipList << m_primaryPlane; - - if (!doAtomicCommit(AtomicCommitMode::Test)) { - qCDebug(KWIN_DRM) << "Atomic test commit to Dpms Off failed. Aborting."; - return false; - } - if (!doAtomicCommit(AtomicCommitMode::Real)) { - qCDebug(KWIN_DRM) << "Atomic commit to Dpms Off failed. This should have never happened! Aborting."; - return false; - } - m_nextPlanesFlipList.clear(); - dpmsFinishOff(); - - return true; -} - -bool DrmOutput::presentAtomically(const QSharedPointer &buffer) { if (!m_backend->session()->isActive()) { - qCWarning(KWIN_DRM) << "Refusing to present output because session is inactive"; + qCWarning(KWIN_DRM) << "session not active!"; return false; } - if (m_pageFlipPending) { - qCWarning(KWIN_DRM) << "Page not yet flipped."; + qCWarning(KWIN_DRM) << "page not flipped yet!"; return false; } - -#if HAVE_EGL_STREAMS - if (m_gpu->useEglStreams() && !m_modesetRequested) { - // EglStreamBackend queues normal page flips through EGL, - // modesets are still performed through DRM-KMS + if (m_pipeline->present(buffer)) { m_pageFlipPending = true; return true; - } -#endif - - m_primaryPlane->setNext(buffer); - m_nextPlanesFlipList << m_primaryPlane; - - if (!doAtomicCommit(AtomicCommitMode::Test)) { - //TODO: When we use planes for layered rendering, fallback to renderer instead. Also for direct scanout? - //TODO: Probably should undo setNext and reset the flip list - qCDebug(KWIN_DRM) << "Atomic test commit failed. Aborting present."; - // go back to previous state - if (m_lastWorkingState.valid) { - m_mode = m_lastWorkingState.mode; - setTransformInternal(m_lastWorkingState.transform); - setGlobalPos(m_lastWorkingState.globalPos); - if (m_primaryPlane) { - m_primaryPlane->setTransformation(m_lastWorkingState.planeTransformations); - } - m_modesetRequested = true; - if (!m_backend->isCursorHidden()) { - // the cursor might need to get rotated - updateCursor(); - showCursor(); - } - setCurrentModeInternal(); - emit screens()->changed(); - } - return false; - } - const bool wasModeset = m_modesetRequested; - if (!doAtomicCommit(AtomicCommitMode::Real)) { - qCDebug(KWIN_DRM) << "Atomic commit failed. This should have never happened! Aborting present."; - //TODO: Probably should undo setNext and reset the flip list - return false; - } - if (wasModeset) { - // store current mode set as new good state - m_lastWorkingState.mode = m_mode; - m_lastWorkingState.transform = transform(); - m_lastWorkingState.globalPos = globalPos(); - if (m_primaryPlane) { - m_lastWorkingState.planeTransformations = m_primaryPlane->transformation(); - } - m_lastWorkingState.valid = true; - m_renderLoop->setRefreshRate(refreshRateForMode(&m_mode)); - } - m_pageFlipPending = true; - return true; -} - -bool DrmOutput::presentLegacy(const QSharedPointer &buffer) -{ - if (m_crtc->next()) { - return false; - } - if (!m_backend->session()->isActive()) { - m_crtc->setNext(buffer); - return false; - } - - // Do we need to set a new mode first? - if (!m_crtc->current() || m_crtc->current()->needsModeChange(buffer.get())) { - if (!setModeLegacy(buffer.get())) { - return false; - } - } - const bool ok = drmModePageFlip(m_gpu->fd(), m_crtc->id(), buffer->bufferId(), DRM_MODE_PAGE_FLIP_EVENT, this) == 0; - if (ok) { - m_crtc->setNext(buffer); - m_pageFlipPending = true; } else { - qCWarning(KWIN_DRM) << "Page flip failed:" << strerror(errno); - } - return ok; -} - -bool DrmOutput::setModeLegacy(DrmBuffer *buffer) -{ - uint32_t connId = m_conn->id(); - if (drmModeSetCrtc(m_gpu->fd(), m_crtc->id(), buffer->bufferId(), 0, 0, &connId, 1, &m_mode) == 0) { - return true; - } else { - qCWarning(KWIN_DRM) << "Mode setting failed"; + qCCritical(KWIN_DRM) << "present failed! This never should've happened!"; return false; } } -bool DrmOutput::doAtomicCommit(AtomicCommitMode mode) -{ - drmModeAtomicReq *req = drmModeAtomicAlloc(); - - auto errorHandler = [this, mode, req] () { - if (mode == AtomicCommitMode::Test) { - // TODO: when we later test overlay planes, make sure we change only the right stuff back - } - if (req) { - drmModeAtomicFree(req); - } - - if (dpmsMode() != m_dpmsModePending) { - qCWarning(KWIN_DRM) << "Setting DPMS failed"; - m_dpmsModePending = dpmsMode(); - if (dpmsMode() != DpmsMode::On) { - dpmsFinishOff(); - } - } - - // TODO: see above, rework later for overlay planes! - for (DrmPlane *p : m_nextPlanesFlipList) { - p->setNext(nullptr); - } - m_nextPlanesFlipList.clear(); - - }; - - if (!req) { - qCWarning(KWIN_DRM) << "DRM: couldn't allocate atomic request"; - errorHandler(); - return false; - } - - uint32_t flags = 0; - // Do we need to set a new mode? - if (m_modesetRequested) { - if (m_dpmsModePending == DpmsMode::On) { - if (drmModeCreatePropertyBlob(m_gpu->fd(), &m_mode, sizeof(m_mode), &m_blobId) != 0) { - qCWarning(KWIN_DRM) << "Failed to create property blob"; - errorHandler(); - return false; - } - } - if (!atomicReqModesetPopulate(req, m_dpmsModePending == DpmsMode::On)){ - qCWarning(KWIN_DRM) << "Failed to populate Atomic Modeset"; - errorHandler(); - return false; - } - flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; - } - - if (mode == AtomicCommitMode::Real) { - if (m_dpmsModePending == DpmsMode::On) { - if (!(flags & DRM_MODE_ATOMIC_ALLOW_MODESET)) { - // TODO: Evaluating this condition should only be necessary, as long as we expect older kernels than 4.10. - flags |= DRM_MODE_ATOMIC_NONBLOCK; - } - -#if HAVE_EGL_STREAMS - if (!m_gpu->useEglStreams()) - // EglStreamBackend uses the NV_output_drm_flip_event EGL extension - // to register the flip event through eglStreamConsumerAcquireAttribNV -#endif - flags |= DRM_MODE_PAGE_FLIP_EVENT; - } - } else { - flags |= DRM_MODE_ATOMIC_TEST_ONLY; - } - - bool ret = true; - // TODO: Make sure when we use more than one plane at a time, that we go through this list in the right order. - for (int i = m_nextPlanesFlipList.size() - 1; 0 <= i; i-- ) { - DrmPlane *p = m_nextPlanesFlipList[i]; - ret &= p->atomicPopulate(req); - } - - if (!ret) { - qCWarning(KWIN_DRM) << "Failed to populate atomic planes. Abort atomic commit!"; - errorHandler(); - return false; - } - - if (drmModeAtomicCommit(m_gpu->fd(), req, flags, this)) { - qCDebug(KWIN_DRM) << "Atomic request failed to commit: " << strerror(errno); - errorHandler(); - return false; - } - - if (mode == AtomicCommitMode::Real && (flags & DRM_MODE_ATOMIC_ALLOW_MODESET)) { - qCDebug(KWIN_DRM) << "Atomic Modeset successful."; - m_modesetRequested = false; - setDpmsModeInternal(m_dpmsModePending); - } - - drmModeAtomicFree(req); - return true; -} - -bool DrmOutput::atomicReqModesetPopulate(drmModeAtomicReq *req, bool enable) -{ - if (enable) { - const QSize mSize = modeSize(); - const QSize bufferSize = m_primaryPlane->next() ? m_primaryPlane->next()->size() : pixelSize(); - const QSize sourceSize = hardwareTransforms() ? bufferSize : mSize; - QRect targetRect = QRect(QPoint(0, 0), mSize); - if (mSize != sourceSize) { - targetRect.setSize(sourceSize.scaled(mSize, Qt::AspectRatioMode::KeepAspectRatio)); - targetRect.setX((mSize.width() - targetRect.width()) / 2); - targetRect.setY((mSize.height() - targetRect.height()) / 2); - } - - m_primaryPlane->setValue(DrmPlane::PropertyIndex::SrcX, 0); - m_primaryPlane->setValue(DrmPlane::PropertyIndex::SrcY, 0); - m_primaryPlane->setValue(DrmPlane::PropertyIndex::SrcW, sourceSize.width() << 16); - m_primaryPlane->setValue(DrmPlane::PropertyIndex::SrcH, sourceSize.height() << 16); - m_primaryPlane->setValue(DrmPlane::PropertyIndex::CrtcX, targetRect.x()); - m_primaryPlane->setValue(DrmPlane::PropertyIndex::CrtcY, targetRect.y()); - m_primaryPlane->setValue(DrmPlane::PropertyIndex::CrtcW, targetRect.width()); - m_primaryPlane->setValue(DrmPlane::PropertyIndex::CrtcH, targetRect.height()); - m_primaryPlane->setValue(DrmPlane::PropertyIndex::CrtcId, m_crtc->id()); - } else { - m_primaryPlane->setCurrent(nullptr); - m_primaryPlane->setNext(nullptr); - - m_primaryPlane->setValue(DrmPlane::PropertyIndex::SrcX, 0); - m_primaryPlane->setValue(DrmPlane::PropertyIndex::SrcY, 0); - m_primaryPlane->setValue(DrmPlane::PropertyIndex::SrcW, 0); - m_primaryPlane->setValue(DrmPlane::PropertyIndex::SrcH, 0); - m_primaryPlane->setValue(DrmPlane::PropertyIndex::CrtcX, 0); - m_primaryPlane->setValue(DrmPlane::PropertyIndex::CrtcY, 0); - m_primaryPlane->setValue(DrmPlane::PropertyIndex::CrtcW, 0); - m_primaryPlane->setValue(DrmPlane::PropertyIndex::CrtcH, 0); - m_primaryPlane->setValue(DrmPlane::PropertyIndex::CrtcId, 0); - } - m_conn->setValue(DrmConnector::PropertyIndex::CrtcId, enable ? m_crtc->id() : 0); - m_crtc->setValue(DrmCrtc::PropertyIndex::ModeId, enable ? m_blobId : 0); - m_crtc->setValue(DrmCrtc::PropertyIndex::Active, enable); - - bool ret = true; - ret &= m_conn->atomicPopulate(req); - ret &= m_crtc->atomicPopulate(req); - - return ret; -} - int DrmOutput::gammaRampSize() const { return m_crtc->gammaRampSize(); @@ -851,7 +418,7 @@ int DrmOutput::gammaRampSize() const bool DrmOutput::setGammaRamp(const GammaRamp &gamma) { - return m_crtc->setGammaRamp(gamma); + return m_pipeline->setGammaRamp(gamma); } } diff --git a/src/plugins/platforms/drm/drm_output.h b/src/plugins/platforms/drm/drm_output.h index 3e8b4c0f87..8e01239d33 100644 --- a/src/plugins/platforms/drm/drm_output.h +++ b/src/plugins/platforms/drm/drm_output.h @@ -3,6 +3,7 @@ This file is part of the KDE project. SPDX-FileCopyrightText: 2015 Martin Gräßlin + SPDX-FileCopyrightText: 2021 Xaver Hugl SPDX-License-Identifier: GPL-2.0-or-later */ @@ -32,6 +33,7 @@ class DrmConnector; class DrmCrtc; class Cursor; class DrmGpu; +class DrmPipeline; class KWIN_EXPORT DrmOutput : public AbstractWaylandOutput { @@ -42,25 +44,21 @@ public: RenderLoop *renderLoop() const override; + bool init(); ///queues deleting the output after a page flip has completed. void teardown(); void releaseBuffers(); - bool showCursor(DrmDumbBuffer *buffer); - bool showCursor(); - bool hideCursor(); - bool updateCursor(); - void moveCursor(); - bool init(drmModeConnector *connector); + bool present(const QSharedPointer &buffer); void pageFlipped(); - bool isDpmsEnabled() const { - // We care for current as well as pending mode in order to allow first present in AMS. - return m_dpmsModePending == DpmsMode::On; - } + bool updateCursor(); + bool moveCursor(); + bool showCursor(); + bool hideCursor(); - DpmsMode dpmsModePending() const { - return m_dpmsModePending; + bool isDpmsEnabled() const { + return m_dpmsEnabled; } const DrmCrtc *crtc() const { @@ -94,36 +92,12 @@ private: // and save the connector ids in the DrmCrtc instance. DrmOutput(DrmBackend *backend, DrmGpu* gpu); - bool presentAtomically(const QSharedPointer &buffer); + void initOutputDevice(); - enum class AtomicCommitMode { - Test, - Real - }; - bool doAtomicCommit(AtomicCommitMode mode); - - bool presentLegacy(const QSharedPointer &buffer); - bool setModeLegacy(DrmBuffer *buffer); - void initOutputDevice(drmModeConnector *connector); - - bool isCurrentMode(const drmModeModeInfo *mode) const; - - void atomicEnable(); - void atomicDisable(); void updateEnablement(bool enable) override; - - bool dpmsAtomicOff(); - bool dpmsLegacyApply(); - - void dpmsFinishOn(); - void dpmsFinishOff(); - - bool atomicReqModesetPopulate(drmModeAtomicReq *req, bool enable); void setDpmsMode(DpmsMode mode) override; void updateMode(int modeIndex) override; void updateMode(uint32_t width, uint32_t height, uint32_t refreshRate); - void setCurrentModeInternal(); - void updateTransform(Transform transform) override; int gammaRampSize() const override; @@ -131,31 +105,18 @@ private: DrmBackend *m_backend; DrmGpu *m_gpu; - DrmConnector *m_conn = nullptr; - DrmCrtc *m_crtc = nullptr; - bool m_lastGbm = false; - drmModeModeInfo m_mode; - DpmsMode m_dpmsModePending = DpmsMode::On; - RenderLoop *m_renderLoop; - - uint32_t m_blobId = 0; DrmPlane *m_primaryPlane = nullptr; DrmPlane *m_cursorPlane = nullptr; - QVector m_nextPlanesFlipList; - bool m_pageFlipPending = false; - bool m_atomicOffPending = false; - bool m_modesetRequested = true; + DrmConnector *m_conn = nullptr; + DrmCrtc *m_crtc = nullptr; - struct { - Transform transform; - drmModeModeInfo mode; - DrmPlane::Transformations planeTransformations; - QPoint globalPos; - bool valid = false; - } m_lastWorkingState; - QScopedPointer m_cursor[2]; - int m_cursorIndex = 0; - bool m_hasNewCursor = false; + RenderLoop *m_renderLoop; + DrmPipeline *m_pipeline = nullptr; + + bool m_dpmsEnabled = true; + QSharedPointer m_cursor; + bool m_firstCommit = true; + bool m_pageFlipPending = false; bool m_deleted = false; }; diff --git a/src/plugins/platforms/drm/drm_pipeline.cpp b/src/plugins/platforms/drm/drm_pipeline.cpp new file mode 100644 index 0000000000..348973874e --- /dev/null +++ b/src/plugins/platforms/drm/drm_pipeline.cpp @@ -0,0 +1,337 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2021 Xaver Hugl + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "drm_pipeline.h" + +#include "logging.h" + +#include "drm_gpu.h" +#include "drm_object_connector.h" +#include "drm_object_crtc.h" +#include "drm_object_plane.h" +#include "drm_buffer.h" +#include "cursor.h" +#include "session.h" +#include "abstract_output.h" + +#include + +namespace KWin +{ + +DrmPipeline::DrmPipeline(void *pageflipUserData, DrmGpu *gpu, DrmConnector *conn, DrmCrtc *crtc, DrmPlane *primaryPlane, DrmPlane *cursorPlane) + : m_pageflipUserData(pageflipUserData), m_gpu(gpu), m_connector(conn), m_crtc(crtc), m_primaryPlane(primaryPlane) +{ + m_cursor.plane = cursorPlane; + const auto &mode = m_connector->currentMode(); + m_mode.mode = mode.mode; + m_mode.sourceSize = mode.size; + m_mode.enabled = true; + m_mode.blobId = -1;// = keep mode +} + +DrmPipeline::~DrmPipeline() +{ + if (m_mode.blobId > 0) { + drmModeDestroyPropertyBlob(m_gpu->fd(), m_mode.blobId); + } + if (m_gamma.blobId > 0) { + drmModeDestroyPropertyBlob(m_gpu->fd(), m_gamma.blobId); + } +} + +bool DrmPipeline::present(const QSharedPointer &buffer) +{ + if (m_gpu->useEglStreams() && !m_mode.changed && !m_gamma.changed) { + // EglStreamBackend queues normal page flips through EGL, + // modesets etc are performed through DRM-KMS + return true; + } + setPrimaryBuffer(buffer); + bool result = m_gpu->atomicModeSetting() ? atomicCommit(false) : presentLegacy(); + if (result) { + m_mode.changed = false; + m_gamma.changed = false; + } else { + qCWarning(KWIN_DRM) << "Present failed!" << strerror(errno); + } + return result; +} + +bool DrmPipeline::test() +{ + if (m_gpu->atomicModeSetting()) { + return atomicCommit(true); + } else { + return true; + } +} + +bool DrmPipeline::atomicCommit(bool testOnly) +{ + uint32_t flags = 0; + drmModeAtomicReq *req = drmModeAtomicAlloc(); + if (!req) { + qCDebug(KWIN_DRM) << "Failed to allocate drmModeAtomicReq!" << strerror(errno); + return false; + } + if (testOnly) { + checkTestBuffer(); + } + m_primaryPlane->setNext(testOnly ? m_testBuffer : m_primaryBuffer); + for (int i = 0; i < m_overlayPlanes.count(); i++) { + m_overlayPlanes[i]->setNext(testOnly ? m_testBuffer : m_overlayBuffers[i]); + } + bool result = populateAtomicValues(req, flags); + if (result && drmModeAtomicCommit(m_gpu->fd(), req, (flags & (~DRM_MODE_PAGE_FLIP_EVENT)) | DRM_MODE_ATOMIC_TEST_ONLY, m_pageflipUserData)) { + result = false; + } + if (!testOnly && result && drmModeAtomicCommit(m_gpu->fd(), req, flags, m_pageflipUserData)) { + qCCritical(KWIN_DRM) << "Atomic commit failed! This never should've happened!" << strerror(errno); + result = false; + } + drmModeAtomicFree(req); + return result; +} + +bool DrmPipeline::populateAtomicValues(drmModeAtomicReq* req, uint32_t &flags) +{ + if (!m_gpu->useEglStreams() && m_mode.enabled) { + flags |= DRM_MODE_PAGE_FLIP_EVENT; + } + if (m_mode.changed) { + flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; + } else { + flags |= DRM_MODE_ATOMIC_NONBLOCK; + } + + m_connector->setValue(DrmConnector::PropertyIndex::CrtcId, m_mode.enabled ? m_crtc->id() : 0); + if (!m_connector->atomicPopulate(req)) { + qCWarning(KWIN_DRM) << "Atomic populate for connector failed!"; + return false; + } + + m_crtc->setValue(DrmCrtc::PropertyIndex::ModeId, m_mode.enabled ? m_mode.blobId : 0); + m_crtc->setValue(DrmCrtc::PropertyIndex::Active, m_mode.enabled ? 1 : 0); + m_crtc->setValue(DrmCrtc::PropertyIndex::Gamma_LUT, m_mode.enabled ? m_gamma.blobId : 0); + if (!m_crtc->atomicPopulate(req)) { + qCWarning(KWIN_DRM) << "Atomic populate for crtc failed!"; + return false; + } + + QSize modesize = QSize(m_mode.mode.hdisplay, m_mode.mode.vdisplay); + m_primaryPlane->setScaled(m_primaryPlane->next() ? m_primaryPlane->next()->size() : modesize, modesize, m_crtc->id(), m_mode.enabled); + if (!m_primaryPlane->atomicPopulate(req)) { + qCWarning(KWIN_DRM) << "Atomic populate for primary plane failed!"; + return false; + } + + if (m_cursor.plane) { + const QSize &size = m_cursor.buffer ? m_cursor.buffer->size() : QSize(0, 0); + m_cursor.plane->setNext(m_cursor.buffer); + m_cursor.plane->set(size, m_cursor.pos, size, m_crtc->id(), m_mode.enabled && m_cursor.buffer != nullptr); + if (!m_cursor.plane->atomicPopulate(req)) { + qCWarning(KWIN_DRM) << "Atomic populate for cursor plane failed!"; + return false; + } + } + + for (const auto &overlay : qAsConst(m_overlayPlanes)) { + overlay->setScaled(overlay->next() ? overlay->next()->size() : modesize, modesize, m_crtc->id(), m_mode.enabled); + if (!overlay->atomicPopulate(req)) { + qCWarning(KWIN_DRM) << "Atomic populate for overlay plane failed!"; + return false; + } + } + + return true; +} + +bool DrmPipeline::presentLegacy() +{ + uint32_t flags = DRM_MODE_PAGE_FLIP_EVENT; + m_crtc->setNext(m_primaryBuffer); + if (drmModePageFlip(m_gpu->fd(), m_crtc->id(), m_primaryBuffer ? m_primaryBuffer->bufferId() : 0, flags, m_pageflipUserData)) { + qCWarning(KWIN_DRM) << "Page flip failed:" << strerror(errno) << m_primaryBuffer; + return false; + } + return true; +} + +bool DrmPipeline::addOverlayPlane(DrmPlane *plane) +{ + if (m_gpu->atomicModeSetting()) { + m_overlayPlanes << plane; + if (!atomicCommit(true)) { + qCWarning(KWIN_DRM) << "Could not add overlay plane!"; + m_overlayPlanes.removeOne(plane); + return false; + } + m_overlayBuffers << nullptr; + } else { + return false; + } + return true; +} + +void DrmPipeline::setPrimaryBuffer(const QSharedPointer &buffer) +{ + m_primaryBuffer = buffer; +} + +bool DrmPipeline::modeset(QSize source, drmModeModeInfo mode) +{ + auto oldMode = m_mode; + m_mode.sourceSize = source; + m_mode.mode = mode; + if (m_gpu->atomicModeSetting()) { + m_mode.changed = true; + if (drmModeCreatePropertyBlob(m_gpu->fd(), &mode, sizeof(drmModeModeInfo), &m_mode.blobId)) { + qCWarning(KWIN_DRM) << "Failed to create property blob for mode!" << strerror(errno); + m_mode = oldMode; + return false; + } + if (!atomicCommit(true)) { + drmModeDestroyPropertyBlob(m_gpu->fd(), m_mode.blobId); + m_mode = oldMode; + return false; + } + if (oldMode.blobId) { + drmModeDestroyPropertyBlob(m_gpu->fd(), oldMode.blobId); + } + } else { + checkTestBuffer(); + uint32_t connId = m_connector->id(); + if (drmModeSetCrtc(m_gpu->fd(), m_crtc->id(), m_testBuffer->bufferId(), 0, 0, &connId, 1, &mode)) { + m_mode = oldMode; + qCWarning(KWIN_DRM) << "Modeset failed:" << strerror(errno); + return false; + } + } + return true; +} + +bool DrmPipeline::setCursor(const QSharedPointer &buffer) +{ + auto oldCursor = m_cursor; + m_cursor.buffer = buffer; + if (m_gpu->atomicModeSetting() && m_cursor.plane) { + if (!atomicCommit(true)) { + qCWarning(KWIN_DRM) << "Could not set cursor!"; + m_cursor = oldCursor; + return false; + } + } else { + const QSize &s = buffer ? buffer->size() : QSize(0, 0); + if (drmModeSetCursor(m_gpu->fd(), m_crtc->id(), m_cursor.buffer ? m_cursor.buffer->handle() : 0, s.width(), s.height())) { + m_cursor = oldCursor; + qCWarning(KWIN_DRM) << "Could not set cursor:" << strerror(errno); + return false; + } + } + return true; +} + +bool DrmPipeline::moveCursor(QPoint pos) +{ + auto cursor = m_cursor; + m_cursor.pos = pos; + if (m_gpu->atomicModeSetting() && m_cursor.plane) { + if (!atomicCommit(true)) { + m_cursor = cursor; + return false; + } + } else { + if (drmModeMoveCursor(m_gpu->fd(), m_crtc->id(), m_cursor.pos.x(), m_cursor.pos.y())) { + m_cursor = cursor; + return false; + } + } + return true; +} + +bool DrmPipeline::setEnablement(bool enabled) +{ + auto oldMode = m_mode; + m_mode.enabled = enabled; + setPrimaryBuffer(nullptr); + if (m_gpu->atomicModeSetting()) { + m_mode.changed = true; + // immediately commit if disabling as there will be no present + if (!atomicCommit(enabled)) { + m_mode = oldMode; + return false; + } + } else { + if (!m_connector->dpms()) { + qCWarning(KWIN_DRM) << "Setting DPMS failed: dpms property missing!"; + m_mode = oldMode; + return false; + } + if (drmModeConnectorSetProperty(m_gpu->fd(), m_connector->id(), m_connector->dpms()->propId(), m_mode.enabled ? DRM_MODE_DPMS_ON : DRM_MODE_DPMS_OFF) < 0) { + qCWarning(KWIN_DRM) << "Setting DPMS failed"; + m_mode = oldMode; + return false; + } + } + return true; +} + +bool DrmPipeline::setGammaRamp(const GammaRamp &ramp) +{ + // Apparently there are old Intel iGPUs that don't have full support + // for setting the gamma ramp with AMS -> fall back to legacy in that case + if (m_gpu->atomicModeSetting() && m_crtc->hasGammaProp()) { + auto oldGamma = m_gamma; + m_gamma.changed = true; + + struct drm_color_lut *gamma = new drm_color_lut[ramp.size()]; + for (uint32_t i = 0; i < ramp.size(); i++) { + gamma[i].red = ramp.red()[i]; + gamma[i].green = ramp.green()[i]; + gamma[i].blue = ramp.blue()[i]; + } + bool result = !drmModeCreatePropertyBlob(m_gpu->fd(), gamma, ramp.size() * sizeof(struct drm_color_lut), &m_gamma.blobId); + delete[] gamma; + if (!result) { + qCWarning(KWIN_DRM) << "Could not create gamma LUT property blob" << strerror(errno); + m_gamma = oldGamma; + return false; + } + if (!atomicCommit(true)) { + qCWarning(KWIN_DRM) << "Setting gamma failed!" << strerror(errno); + drmModeDestroyPropertyBlob(m_gpu->fd(), m_gamma.blobId); + m_gamma = oldGamma; + return false; + } + if (oldGamma.blobId) { + drmModeDestroyPropertyBlob(m_gpu->fd(), oldGamma.blobId); + } + } else { + uint16_t *red = const_cast(ramp.red()); + uint16_t *green = const_cast(ramp.green()); + uint16_t *blue = const_cast(ramp.blue()); + bool result = !drmModeCrtcSetGamma(m_gpu->fd(), m_crtc->id(), ramp.size(), red, green, blue); + if (!result) { + qCWarning(KWIN_DRM) << "setting gamma failed!" << strerror(errno); + return false; + } + } + return true; +} + +void DrmPipeline::checkTestBuffer() +{ + if (m_testBuffer && m_testBuffer->size() == m_mode.sourceSize) { + return; + } + m_testBuffer = m_gpu->createTestbuffer(m_mode.sourceSize); +} + +} diff --git a/src/plugins/platforms/drm/drm_pipeline.h b/src/plugins/platforms/drm/drm_pipeline.h new file mode 100644 index 0000000000..304e0555d5 --- /dev/null +++ b/src/plugins/platforms/drm/drm_pipeline.h @@ -0,0 +1,94 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2021 Xaver Hugl + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include +#include +#include +#include + +#include + +namespace KWin { + +class DrmGpu; +class DrmConnector; +class DrmCrtc; +class DrmPlane; +class DrmBuffer; +class DrmDumbBuffer; +class GammaRamp; + +class DrmPipeline +{ +public: + DrmPipeline(void *pageflipUserData, DrmGpu *gpu, DrmConnector *conn, DrmCrtc *crtc, DrmPlane *primaryPlane, DrmPlane *cursorPlane); + ~DrmPipeline(); + + /** + * tests the pending commit first and commits it if the test passes + * if the test fails, there is a guarantee for no lasting changes + */ + bool present(const QSharedPointer &buffer); + + /** + * tests the pending commit + * always returns true in legacy mode! + */ + bool test(); + + bool modeset(QSize source, drmModeModeInfo mode); + bool setCursor(const QSharedPointer &buffer); + bool setEnablement(bool enable); + bool setGammaRamp(const GammaRamp &ramp); + + void setPrimaryBuffer(const QSharedPointer &buffer); + bool moveCursor(QPoint pos); + + bool addOverlayPlane(DrmPlane *plane); + +private: + bool atomicCommit(bool testOnly); + bool populateAtomicValues(drmModeAtomicReq *req, uint32_t &flags); + bool presentLegacy(); + void checkTestBuffer(); + + void *m_pageflipUserData = nullptr; + DrmGpu *m_gpu = nullptr; + DrmConnector *m_connector = nullptr; + DrmCrtc *m_crtc = nullptr; + + DrmPlane *m_primaryPlane = nullptr; + QSharedPointer m_primaryBuffer; + QSharedPointer m_testBuffer; + + QVector m_overlayPlanes; + QVector> m_overlayBuffers; + + struct { + bool changed = false; + bool enabled = true; + QSize sourceSize = QSize(-1, -1); + drmModeModeInfo mode; + uint32_t blobId = 0; + } m_mode; + struct { + DrmPlane *plane = nullptr; + QPoint pos = QPoint(100, 100); + QSharedPointer buffer; + } m_cursor; + struct { + bool changed = false; + uint32_t blobId = 0; + } m_gamma; + +}; + +}