From e2a0863843f92ab1f2f8d0c785a86570fc04d774 Mon Sep 17 00:00:00 2001 From: Xaver Hugl Date: Tue, 28 Sep 2021 10:29:56 +0200 Subject: [PATCH] platforms/drm: more dynamic crtc assignment Hardware constraints limit the number of crtcs and which connector + crtc combinations can work together. The current code is searching for working combinations when a hotplug happens but that's not enough, it also needs to happen when the user enables or disables outputs and when modesets are done, and the configuration change needs to be applied with a single atomic commit. This commit removes the hard dependency of DrmPipeline on crtcs by moving the pending state of outputs from the drm objects to DrmPipeline itself, which ensures that it's independent from the set of drm objects currently used. It also changes requests from KScreen to be applied truly atomically. --- src/CMakeLists.txt | 1 + src/abstract_output.cpp | 5 - src/abstract_output.h | 7 - src/abstract_wayland_output.cpp | 71 +--- src/abstract_wayland_output.h | 20 +- src/backends/drm/drm_backend.cpp | 81 ++-- src/backends/drm/drm_backend.h | 3 +- src/backends/drm/drm_gpu.cpp | 249 +++++------ src/backends/drm/drm_gpu.h | 4 +- src/backends/drm/drm_lease_output.cpp | 20 +- src/backends/drm/drm_lease_output.h | 3 +- src/backends/drm/drm_object_connector.cpp | 7 + src/backends/drm/drm_object_connector.h | 4 + src/backends/drm/drm_output.cpp | 191 +++++---- src/backends/drm/drm_output.h | 13 +- src/backends/drm/drm_pipeline.cpp | 485 +++++++++------------- src/backends/drm/drm_pipeline.h | 69 +-- src/backends/drm/drm_property.h | 7 + src/backends/drm/drm_virtual_output.cpp | 11 - src/backends/drm/drm_virtual_output.h | 2 - src/backends/drm/egl_stream_backend.cpp | 4 +- src/platform.cpp | 75 ++-- src/platform.h | 6 + src/waylandoutputconfig.cpp | 39 ++ src/waylandoutputconfig.h | 44 ++ 25 files changed, 686 insertions(+), 735 deletions(-) create mode 100644 src/waylandoutputconfig.cpp create mode 100644 src/waylandoutputconfig.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1b1d189d13..36ff38f6c7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -135,6 +135,7 @@ set(kwin_SRCS wayland_server.cpp waylandclient.cpp waylandoutput.cpp + waylandoutputconfig.cpp waylandoutputdevicev2.cpp waylandshellintegration.cpp window_property_notify_x11_filter.cpp diff --git a/src/abstract_output.cpp b/src/abstract_output.cpp index 717b827dd3..4c6f33a17e 100644 --- a/src/abstract_output.cpp +++ b/src/abstract_output.cpp @@ -100,11 +100,6 @@ void AbstractOutput::setEnabled(bool enable) Q_UNUSED(enable) } -void AbstractOutput::applyChanges(const KWaylandServer::OutputChangeSetV2 *changeSet) -{ - Q_UNUSED(changeSet) -} - bool AbstractOutput::isInternal() const { return false; diff --git a/src/abstract_output.h b/src/abstract_output.h index 8183715c52..ed2fca80ac 100644 --- a/src/abstract_output.h +++ b/src/abstract_output.h @@ -117,13 +117,6 @@ public: */ virtual void setEnabled(bool enable); - /** - * This sets the changes and tests them against the specific output. - * - * Default implementation does nothing - */ - virtual void applyChanges(const KWaylandServer::OutputChangeSetV2 *changeSet); - /** * Returns geometry of this output in device independent pixels. */ diff --git a/src/abstract_wayland_output.cpp b/src/abstract_wayland_output.cpp index 373d925033..a8634db9a5 100644 --- a/src/abstract_wayland_output.cpp +++ b/src/abstract_wayland_output.cpp @@ -9,6 +9,7 @@ */ #include "abstract_wayland_output.h" #include "screens.h" +#include "waylandoutputconfig.h" // KWayland #include @@ -20,11 +21,6 @@ namespace KWin { -static AbstractWaylandOutput::Transform outputDeviceTransformToKWinTransform(KWaylandServer::OutputDeviceV2Interface::Transform transform) -{ - return static_cast(transform); -} - AbstractWaylandOutput::AbstractWaylandOutput(QObject *parent) : AbstractOutput(parent) { @@ -152,59 +148,18 @@ void AbstractWaylandOutput::setSubPixelInternal(SubPixel subPixel) m_subPixel = subPixel; } -void AbstractWaylandOutput::applyChanges(const KWaylandServer::OutputChangeSetV2 *changeSet) +void AbstractWaylandOutput::applyChanges(const WaylandOutputConfig &config) { + auto props = config.constChangeSet(this); Q_EMIT aboutToChange(); - qCDebug(KWIN_CORE) << "Apply changes to the Wayland output."; - bool emitModeChanged = false; - bool overallSizeCheckNeeded = false; + setEnabled(props->enabled); + updateTransform(props->transform); + moveTo(props->pos); + setScale(props->scale); + setVrrPolicy(props->vrrPolicy); - // Enablement changes are handled by platform. - if (changeSet->sizeChanged() || changeSet->refreshRateChanged()) { - qCDebug(KWIN_CORE) << "Setting new mode:" << changeSet->size() << changeSet->refreshRate(); - updateMode(changeSet->size(), changeSet->refreshRate()); - emitModeChanged = true; - } - if (changeSet->transformChanged()) { - qCDebug(KWIN_CORE) << "Server setting transform: " << changeSet->transform(); - auto transform = outputDeviceTransformToKWinTransform(changeSet->transform()); - updateTransform(transform); - emitModeChanged = true; - } - if (changeSet->positionChanged()) { - qCDebug(KWIN_CORE) << "Server setting position: " << changeSet->position(); - moveTo(changeSet->position()); - // may just work already! - overallSizeCheckNeeded = true; - } - if (changeSet->scaleChanged()) { - qCDebug(KWIN_CORE) << "Setting scale:" << changeSet->scale(); - setScale(changeSet->scale()); - emitModeChanged = true; - } - if (changeSet->overscanChanged()) { - qCDebug(KWIN_CORE) << "Setting overscan:" << changeSet->overscan(); - setOverscan(changeSet->overscan()); - } - if (changeSet->vrrPolicyChanged()) { - qCDebug(KWIN_CORE) << "Setting VRR Policy:" << changeSet->vrrPolicy(); - setVrrPolicy(static_cast(changeSet->vrrPolicy())); - } - if (changeSet->rgbRangeChanged()) { - qDebug(KWIN_CORE) << "Setting rgb range:" << changeSet->rgbRange(); - setRgbRange(static_cast(changeSet->rgbRange())); - } Q_EMIT changed(); - - overallSizeCheckNeeded |= emitModeChanged; - if (overallSizeCheckNeeded) { - Q_EMIT screens()->changed(); - } - - if (emitModeChanged) { - Q_EMIT currentModeChanged(); - } } bool AbstractWaylandOutput::isEnabled() const @@ -386,11 +341,6 @@ uint32_t AbstractWaylandOutput::overscan() const return m_overscan; } -void AbstractWaylandOutput::setOverscan(uint32_t overscan) -{ - Q_UNUSED(overscan); -} - void AbstractWaylandOutput::setVrrPolicy(RenderLoop::VrrPolicy policy) { if (renderLoop()->vrrPolicy() != policy && (m_capabilities & Capability::Vrr)) { @@ -419,11 +369,6 @@ AbstractWaylandOutput::RgbRange AbstractWaylandOutput::rgbRange() const return m_rgbRange; } -void AbstractWaylandOutput::setRgbRange(RgbRange range) -{ - Q_UNUSED(range) -} - void AbstractWaylandOutput::setRgbRangeInternal(RgbRange range) { if (m_rgbRange != range) { diff --git a/src/abstract_wayland_output.h b/src/abstract_wayland_output.h index 20b894e8f1..72d9f2912d 100644 --- a/src/abstract_wayland_output.h +++ b/src/abstract_wayland_output.h @@ -18,14 +18,11 @@ #include #include -namespace KWaylandServer -{ -class OutputChangeSet; -} - namespace KWin { +class WaylandOutputConfig; + /** * Generic output representation in a Wayland session */ @@ -121,7 +118,7 @@ public: void moveTo(const QPoint &pos); void setScale(qreal scale); - void applyChanges(const KWaylandServer::OutputChangeSetV2 *changeSet) override; + void applyChanges(const WaylandOutputConfig &config); bool isEnabled() const override; void setEnabled(bool enable) override; @@ -136,7 +133,6 @@ public: virtual void setDpmsMode(DpmsMode mode); uint32_t overscan() const; - virtual void setOverscan(uint32_t overscan); /** * Returns a matrix that can translate into the display's coordinates system @@ -150,8 +146,6 @@ public: void setVrrPolicy(RenderLoop::VrrPolicy policy); RenderLoop::VrrPolicy vrrPolicy() const; - - virtual void setRgbRange(RgbRange range); RgbRange rgbRange() const; bool isPlaceholder() const; @@ -183,14 +177,6 @@ protected: virtual void updateEnablement(bool enable) { Q_UNUSED(enable); } - virtual void updateMode(const QSize &size, uint32_t refreshRate) - { - Q_UNUSED(size); - Q_UNUSED(refreshRate); - } - virtual void applyMode(int modeIndex) { - Q_UNUSED(modeIndex); - } virtual void updateTransform(Transform transform) { Q_UNUSED(transform); } diff --git a/src/backends/drm/drm_backend.cpp b/src/backends/drm/drm_backend.cpp index 316af63d73..4ece2296f1 100644 --- a/src/backends/drm/drm_backend.cpp +++ b/src/backends/drm/drm_backend.cpp @@ -25,6 +25,7 @@ #include "egl_multi_backend.h" #include "drm_pipeline.h" #include "drm_virtual_output.h" +#include "waylandoutputconfig.h" #if HAVE_GBM #include "egl_gbm_backend.h" #include @@ -351,7 +352,7 @@ void DrmBackend::updateOutputs() } }); if (oldOutputs != m_outputs) { - readOutputsConfiguration(); + readOutputsConfiguration(m_outputs); } Q_EMIT screensQueried(); } @@ -443,40 +444,43 @@ namespace KWinKScreenIntegration } } -void DrmBackend::readOutputsConfiguration() +void DrmBackend::readOutputsConfiguration(const QVector &outputs) { - if (m_outputs.isEmpty()) { - return; - } - const auto outputsInfo = KWinKScreenIntegration::outputsConfig(m_outputs); + const auto outputsInfo = KWinKScreenIntegration::outputsConfig(outputs); AbstractOutput *primaryOutput = m_outputs.constFirst(); + WaylandOutputConfig cfg; // default position goes from left to right QPoint pos(0, 0); - for (auto it = m_outputs.begin(); it != m_outputs.end(); ++it) { - const QJsonObject outputInfo = outputsInfo[*it]; - qCDebug(KWIN_DRM) << "Reading output configuration for " << *it; + for (const auto &output : qAsConst(outputs)) { + auto props = cfg.changeSet(output); + const QJsonObject outputInfo = outputsInfo[output]; + qCDebug(KWIN_DRM) << "Reading output configuration for " << output; if (!outputInfo.isEmpty()) { if (outputInfo["primary"].toBool()) { - primaryOutput = *it; + primaryOutput = output; } + props->enabled = outputInfo["enabled"].toBool(true); const QJsonObject pos = outputInfo["pos"].toObject(); - (*it)->moveTo({pos["x"].toInt(), pos["y"].toInt()}); + props->pos = QPoint(pos["x"].toInt(), pos["y"].toInt()); if (const QJsonValue scale = outputInfo["scale"]; !scale.isUndefined()) { - (*it)->setScale(scale.toDouble(1.)); + props->scale = scale.toDouble(1.); } - (*it)->updateTransform(KWinKScreenIntegration::toDrmTransform(outputInfo["rotation"].toInt())); + props->transform = KWinKScreenIntegration::toDrmTransform(outputInfo["rotation"].toInt()); if (const QJsonObject mode = outputInfo["mode"].toObject(); !mode.isEmpty()) { const QJsonObject size = mode["size"].toObject(); - (*it)->updateMode(QSize(size["width"].toInt(), size["height"].toInt()), mode["refresh"].toDouble() * 1000); + props->modeSize = QSize(size["width"].toInt(), size["height"].toInt()); + props->refreshRate = mode["refresh"].toDouble() * 1000; } } else { - (*it)->moveTo(pos); - (*it)->updateTransform(DrmOutput::Transform::Normal); + props->enabled = true; + props->pos = pos; + props->transform = DrmOutput::Transform::Normal; } - pos.setX(pos.x() + (*it)->geometry().width()); + pos.setX(pos.x() + output->geometry().width()); } + applyOutputChanges(cfg); setPrimaryOutput(primaryOutput); } @@ -497,12 +501,9 @@ void DrmBackend::enableOutput(DrmAbstractOutput *output, bool enable) } } else { if (m_enabledOutputs.count() == 1) { - for (const auto &o : qAsConst(m_outputs)) { - if (o != output) { - o->setEnabled(true); - break; - } - } + auto outputs = m_outputs; + outputs.removeOne(output); + readOutputsConfiguration(outputs); } if (m_enabledOutputs.count() == 1 && !kwinApp()->isTerminating()) { qCDebug(KWIN_DRM) << "adding placeholder output"; @@ -683,7 +684,7 @@ QString DrmBackend::supportInformation() const AbstractOutput *DrmBackend::createVirtualOutput(const QString &name, const QSize &size, double scale) { auto output = primaryGpu()->createVirtualOutput(name, size * scale, scale, DrmGpu::Full); - readOutputsConfiguration(); + readOutputsConfiguration(m_outputs); Q_EMIT screensQueried(); return output; } @@ -733,4 +734,36 @@ DrmGpu *DrmBackend::findGpuByFd(int fd) const return nullptr; } +bool DrmBackend::applyOutputChanges(const WaylandOutputConfig &config) +{ + QVector changed; + for (const auto &gpu : qAsConst(m_gpus)) { + const auto &outputs = gpu->outputs(); + for (const auto &o : outputs) { + DrmOutput *output = qobject_cast(o); + if (!output) { + // virtual outputs don't need testing + continue; + } + output->queueChanges(config); + changed << output; + } + if (!gpu->testPendingConfiguration()) { + for (const auto &output : qAsConst(changed)) { + output->revertQueuedChanges(); + } + return false; + } + } + for (const auto &output : qAsConst(m_outputs)) { + if (auto drmOutput = qobject_cast(output)) { + drmOutput->applyQueuedChanges(config); + } else { + output->applyChanges(config); + } + }; + updateCursor(); + return true; +} + } diff --git a/src/backends/drm/drm_backend.h b/src/backends/drm/drm_backend.h index f06b973ea9..097257265d 100644 --- a/src/backends/drm/drm_backend.h +++ b/src/backends/drm/drm_backend.h @@ -82,6 +82,7 @@ protected: void doHideCursor() override; void doShowCursor() override; void doSetSoftwareCursor() override; + bool applyOutputChanges(const WaylandOutputConfig &config) override; private: friend class DrmGpu; @@ -94,7 +95,7 @@ private: void updateCursor(); void moveCursor(); void initCursor(); - void readOutputsConfiguration(); + void readOutputsConfiguration(const QVector &outputs); void handleUdevEvent(); DrmGpu *addGpu(const QString &fileName); diff --git a/src/backends/drm/drm_gpu.cpp b/src/backends/drm/drm_gpu.cpp index 9349bfb706..cc4f141308 100644 --- a/src/backends/drm/drm_gpu.cpp +++ b/src/backends/drm/drm_gpu.cpp @@ -183,18 +183,14 @@ void DrmGpu::initDrmResources() } if (m_planes.isEmpty()) { qCWarning(KWIN_DRM) << "Failed to create any plane. Falling back to legacy mode on GPU " << m_devNode; - m_atomicModeSetting = false; - } else { - m_atomicModeSetting = true; } } else { qCWarning(KWIN_DRM) << "Failed to get plane resources. Falling back to legacy mode on GPU " << m_devNode; - m_atomicModeSetting = false; } } else { qCWarning(KWIN_DRM) << "drmSetClientCap for Atomic Mode Setting failed. Using legacy mode on GPU" << m_devNode; - m_atomicModeSetting = false; } + m_atomicModeSetting = !m_planes.isEmpty(); DrmScopedPointer resources(drmModeGetResources(m_fd)); if (!resources) { @@ -266,6 +262,7 @@ bool DrmGpu::updateOutputs() continue; } m_connectors << c; + m_pipelines << c->pipeline(); } else { (*it)->updateProperties(); if ((*it)->isConnected()) { @@ -280,17 +277,30 @@ bool DrmGpu::updateOutputs() removeLeaseOutput(leaseOutput); } m_connectors.removeOne(connector); + m_pipelines.removeOne(connector->pipeline()); delete connector; } // find unused and connected connectors - QVector connectedConnectors; for (const auto &conn : qAsConst(m_connectors)) { auto output = findOutput(conn->id()); if (conn->isConnected()) { - connectedConnectors << conn; if (output) { output->updateModes(); + } else if (!findLeaseOutput(conn->id())) { + qCDebug(KWIN_DRM, "New %soutput on GPU %s: %s", conn->isNonDesktop() ? "non-desktop " : "", qPrintable(m_devNode), qPrintable(conn->modelName())); + if (conn->isNonDesktop()) { + auto leaseOutput = new DrmLeaseOutput(conn->pipeline(), m_leaseDevice); + m_leaseOutputs << leaseOutput; + } else { + auto output = new DrmOutput(conn->pipeline()); + if (!output->initCursor(m_cursorSize)) { + m_platform->setSoftwareCursor(true); + } + m_drmOutputs << output; + m_outputs << output; + Q_EMIT outputAdded(output); + } } } else if (output) { removeOutput(output); @@ -302,170 +312,100 @@ bool DrmGpu::updateOutputs() // update crtc properties for (const auto &crtc : qAsConst(m_crtcs)) { crtc->updateProperties(); + crtc->setLegacyCursor(); } // update plane properties for (const auto &plane : qAsConst(m_planes)) { plane->updateProperties(); } - // stash away current pipelines of active outputs - QMap oldPipelines; - for (const auto &output : qAsConst(m_drmOutputs)) { - if (!output->isEnabled()) { - // create render resources for findWorkingCombination - Q_EMIT outputEnabled(output); + if (testPendingConfiguration()) { + for (const auto &pipeline : qAsConst(m_pipelines)) { + pipeline->applyPendingChanges(); + if (!pipeline->pending.crtc && pipeline->output()) { + pipeline->output()->setEnabled(false); + } + } + } else { + for (const auto &pipeline : qAsConst(m_pipelines)) { + pipeline->revertPendingChanges(); } - m_pipelines.removeOne(output->pipeline()); - oldPipelines.insert(output, output->pipeline()); - output->setPipeline(nullptr); } + m_leaseDevice->setDrmMaster(true); + return true; +} +bool DrmGpu::checkCrtcAssignment(QVector connectors, QVector crtcs) +{ + if (connectors.isEmpty() || crtcs.isEmpty()) { + if (m_pipelines.isEmpty()) { + // nothing to do + return true; + } + // remaining connectors can't be powered + for (const auto &conn : qAsConst(connectors)) { + qCWarning(KWIN_DRM) << "disabling connector" << conn->modelName() << "without a crtc"; + conn->pipeline()->pending.crtc = nullptr; + } + return DrmPipeline::commitPipelines(m_pipelines, DrmPipeline::CommitMode::Test); + } + auto connector = connectors.takeFirst(); + auto pipeline = connector->pipeline(); + if (const auto &output = pipeline->output(); output && !pipeline->pending.active) { + // disabled pipelines don't need CRTCs + pipeline->pending.crtc = nullptr; + return checkCrtcAssignment(connectors, crtcs); + } if (m_atomicModeSetting) { - // sort outputs by being already connected (to any CRTC) so that already working outputs get preferred - std::sort(connectedConnectors.begin(), connectedConnectors.end(), [](auto c1, auto c2){ - return c1->getProp(DrmConnector::PropertyIndex::CrtcId)->current() > c2->getProp(DrmConnector::PropertyIndex::CrtcId)->current(); + // try the crtc that this connector is already connected to first + std::sort(crtcs.begin(), crtcs.end(), [connector](auto c1, auto c2){ + Q_UNUSED(c2) + return connector->getProp(DrmConnector::PropertyIndex::CrtcId)->pending() == c1->id(); }); } - auto connectors = connectedConnectors; + auto encoders = connector->encoders(); + for (const auto &encoder : encoders) { + DrmScopedPointer enc(drmModeGetEncoder(m_fd, encoder)); + if (!enc) { + continue; + } + for (const auto &crtc : qAsConst(crtcs)) { + if ((enc->possible_crtcs & (1 << crtc->pipeIndex()))) { + auto crtcsLeft = crtcs; + crtcsLeft.removeOne(crtc); + pipeline->pending.crtc = crtc; + if (checkCrtcAssignment(connectors, crtcsLeft)) { + return true; + } + } + } + } + return false; +} + +bool DrmGpu::testPendingConfiguration() +{ + QVector connectors; + for (const auto &conn : qAsConst(m_connectors)) { + if (conn->isConnected()) { + connectors << conn; + } + } auto crtcs = m_crtcs; // don't touch resources that are leased for (const auto &output : qAsConst(m_leaseOutputs)) { if (output->lease()) { connectors.removeOne(output->pipeline()->connector()); - crtcs.removeOne(output->pipeline()->crtc()); - } else { - m_pipelines.removeOne(output->pipeline()); + crtcs.removeOne(output->pipeline()->pending.crtc); } } - auto config = findWorkingCombination({}, connectors, crtcs); - if (config.isEmpty() && !connectors.isEmpty()) { - qCCritical(KWIN_DRM) << "DrmGpu::findWorkingCombination failed to find any functional combinations! Reverting to the old configuration!"; - for (auto it = oldPipelines.begin(); it != oldPipelines.end(); it++) { - it.value()->setOutput(it.key()); - config << it.value(); - } - for (const auto &leaseOutput : qAsConst(m_leaseOutputs)) { - if (!leaseOutput->lease()) { - config << leaseOutput->pipeline(); - } - } - } else { - for (const auto &pipeline : qAsConst(oldPipelines)) { - delete pipeline; - } - } - m_pipelines << config; - - for (auto it = config.crbegin(); it != config.crend(); it++) { - const auto &pipeline = *it; - auto output = pipeline->output(); - if (pipeline->connector()->isNonDesktop()) { - if (const auto &leaseOutput = findLeaseOutput(pipeline->connector()->id())) { - leaseOutput->setPipeline(pipeline); - } else { - qCDebug(KWIN_DRM, "New non-desktop output on GPU %s: %s", qPrintable(m_devNode), qPrintable(pipeline->connector()->modelName())); - m_leaseOutputs << new DrmLeaseOutput(pipeline, m_leaseDevice); - } - pipeline->setActive(false); - } else if (m_outputs.contains(output)) { - // restore output properties - if (output->isEnabled()) { - output->updateTransform(output->transform()); - if (output->dpmsMode() != AbstractWaylandOutput::DpmsMode::On) { - pipeline->setActive(false); - } - } else { - pipeline->setActive(false); - Q_EMIT outputDisabled(output); - } - } else { - qCDebug(KWIN_DRM).nospace() << "New output on GPU " << m_devNode << ": " << pipeline->connector()->modelName(); - if (!output->initCursor(m_cursorSize)) { - m_platform->setSoftwareCursorForced(true); - } - m_outputs << output; - m_drmOutputs << output; - Q_EMIT outputAdded(output); - } - } - - m_leaseDevice->setDrmMaster(true); - return true; -} - -QVector DrmGpu::findWorkingCombination(const QVector &pipelines, QVector connectors, QVector crtcs) -{ - if (connectors.isEmpty() || crtcs.isEmpty()) { - // no further pipelines can be added -> test configuration - if (pipelines.isEmpty() || commitCombination(pipelines)) { - return pipelines; - } else { - return {}; - } - } - auto connector = connectors.takeFirst(); - const auto encoders = connector->encoders(); - if (m_atomicModeSetting) { - // try the crtc that this connector is already connected to first - std::sort(crtcs.begin(), crtcs.end(), [connector](auto c1, auto c2){ - Q_UNUSED(c2) - if (connector->getProp(DrmConnector::PropertyIndex::CrtcId)->current() == c1->id()) { - return true; - } else { - return false; - } + // sort outputs by being already connected (to any CRTC) so that already working outputs get preferred + std::sort(connectors.begin(), connectors.end(), [](auto c1, auto c2){ + return c1->getProp(DrmConnector::PropertyIndex::CrtcId)->current() > c2->getProp(DrmConnector::PropertyIndex::CrtcId)->current(); }); } - - auto recurse = [this, connector, connectors, crtcs, pipelines] (DrmCrtc *crtc) { - auto pipeline = new DrmPipeline(this, connector, crtc); - auto crtcsLeft = crtcs; - crtcsLeft.removeOne(crtc); - auto allPipelines = pipelines; - allPipelines << pipeline; - auto ret = findWorkingCombination(allPipelines, connectors, crtcsLeft); - if (ret.isEmpty()) { - delete pipeline; - } - return ret; - }; - for (const auto &encoderId : encoders) { - DrmScopedPointer encoder(drmModeGetEncoder(m_fd, encoderId)); - for (const auto &crtc : qAsConst(crtcs)) { - if (auto workingPipelines = recurse(crtc); !workingPipelines.isEmpty()) { - return workingPipelines; - } - } - } - return {}; -} - -bool DrmGpu::commitCombination(const QVector &pipelines) -{ - for (const auto &pipeline : pipelines) { - auto output = findOutput(pipeline->connector()->id()); - if (output) { - output->setPipeline(pipeline); - pipeline->setOutput(output); - } else if (!pipeline->connector()->isNonDesktop()) { - output = new DrmOutput(this, pipeline); - Q_EMIT outputEnabled(output);// create render resources for the test - } - pipeline->setup(); - } - - if (DrmPipeline::commitPipelines(pipelines, DrmPipeline::CommitMode::Test)) { - return true; - } else { - for (const auto &pipeline : qAsConst(pipelines)) { - if (!m_outputs.contains(pipeline->output())) { - Q_EMIT outputDisabled(pipeline->output()); - delete pipeline->output(); - } - } - return false; - } + return checkCrtcAssignment(connectors, crtcs); } DrmOutput *DrmGpu::findOutput(quint32 connector) @@ -585,10 +525,7 @@ void DrmGpu::removeOutput(DrmOutput *output) m_drmOutputs.removeOne(output); m_outputs.removeOne(output); Q_EMIT outputRemoved(output); - auto pipeline = output->m_pipeline; delete output; - m_pipelines.removeOne(pipeline); - delete pipeline; } AbstractEglDrmBackend *DrmGpu::eglBackend() const @@ -662,7 +599,10 @@ void DrmGpu::handleLeaseRequest(KWaylandServer::DrmLeaseV1Interface *leaseReques for (const auto &connector : conns) { auto output = qobject_cast(connector); if (m_leaseOutputs.contains(output) && !output->lease()) { - output->addLeaseObjects(objects); + if (!output->addLeaseObjects(objects)) { + leaseRequest->deny(); + return; + } outputs << output; } } @@ -704,10 +644,7 @@ void DrmGpu::removeLeaseOutput(DrmLeaseOutput *output) { qCDebug(KWIN_DRM) << "Removing leased output" << output; m_leaseOutputs.removeOne(output); - auto pipeline = output->pipeline(); delete output; - m_pipelines.removeOne(pipeline); - delete pipeline; } QVector DrmGpu::outputs() const diff --git a/src/backends/drm/drm_gpu.h b/src/backends/drm/drm_gpu.h index d9bd858430..986651e4bb 100644 --- a/src/backends/drm/drm_gpu.h +++ b/src/backends/drm/drm_gpu.h @@ -69,6 +69,7 @@ public: QVector outputs() const; const QVector pipelines() const; + bool testPendingConfiguration(); void setGbmDevice(gbm_device *d); void setEglDisplay(EGLDisplay display); @@ -96,8 +97,7 @@ private: void removeLeaseOutput(DrmLeaseOutput *output); void initDrmResources(); - QVector findWorkingCombination(const QVector &pipelines, QVector connectors, QVector crtcs); - bool commitCombination(const QVector &pipelines); + bool checkCrtcAssignment(QVector connectors, QVector crtcs); void handleLeaseRequest(KWaylandServer::DrmLeaseV1Interface *leaseRequest); void handleLeaseRevoked(KWaylandServer::DrmLeaseV1Interface *lease); diff --git a/src/backends/drm/drm_lease_output.cpp b/src/backends/drm/drm_lease_output.cpp index 8da9fa3193..766c3add23 100644 --- a/src/backends/drm/drm_lease_output.cpp +++ b/src/backends/drm/drm_lease_output.cpp @@ -39,14 +39,19 @@ DrmLeaseOutput::~DrmLeaseOutput() qCDebug(KWIN_DRM) << "revoking lease offer for connector" << m_pipeline->connector()->id(); } -void DrmLeaseOutput::addLeaseObjects(QVector &objectList) +bool DrmLeaseOutput::addLeaseObjects(QVector &objectList) { + if (!m_pipeline->pending.crtc) { + qCWarning(KWIN_DRM) << "Can't lease connector: No suitable crtc available"; + return false; + } qCDebug(KWIN_DRM) << "adding connector" << m_pipeline->connector()->id() << "to lease"; objectList << m_pipeline->connector()->id(); - objectList << m_pipeline->crtc()->id(); - if (m_pipeline->primaryPlane()) { - objectList << m_pipeline->primaryPlane()->id(); + objectList << m_pipeline->pending.crtc->id(); + if (m_pipeline->pending.crtc->primaryPlane()) { + objectList << m_pipeline->pending.crtc->primaryPlane()->id(); } + return true; } void DrmLeaseOutput::leased(KWaylandServer::DrmLeaseV1Interface *lease) @@ -60,11 +65,4 @@ void DrmLeaseOutput::leaseEnded() m_lease = nullptr; } -void DrmLeaseOutput::setPipeline(DrmPipeline *pipeline) -{ - Q_ASSERT_X(pipeline->connector() == m_pipeline->connector(), "DrmLeaseOutput::setPipeline", "Pipeline with wrong connector set!"); - delete m_pipeline; - m_pipeline = pipeline; -} - } diff --git a/src/backends/drm/drm_lease_output.h b/src/backends/drm/drm_lease_output.h index b4bf2b7f28..1b992b6fb9 100644 --- a/src/backends/drm/drm_lease_output.h +++ b/src/backends/drm/drm_lease_output.h @@ -31,7 +31,7 @@ public: DrmLeaseOutput(DrmPipeline *pipeline, KWaylandServer::DrmLeaseDeviceV1Interface *leaseDevice); ~DrmLeaseOutput() override; - void addLeaseObjects(QVector &objectList); + bool addLeaseObjects(QVector &objectList); void leased(KWaylandServer::DrmLeaseV1Interface *lease); void leaseEnded(); @@ -42,7 +42,6 @@ public: DrmPipeline *pipeline() const { return m_pipeline; } - void setPipeline(DrmPipeline *pipeline); private: DrmPipeline *m_pipeline; diff --git a/src/backends/drm/drm_object_connector.cpp b/src/backends/drm/drm_object_connector.cpp index b887d06b82..d5336a7ac7 100644 --- a/src/backends/drm/drm_object_connector.cpp +++ b/src/backends/drm/drm_object_connector.cpp @@ -11,6 +11,7 @@ #include "drm_gpu.h" #include "drm_pointer.h" #include "logging.h" +#include "drm_pipeline.h" #include // frameworks @@ -42,6 +43,7 @@ DrmConnector::DrmConnector(DrmGpu *gpu, uint32_t connectorId) QByteArrayLiteral("Limited 16:235") }), }, DRM_MODE_OBJECT_CONNECTOR) + , m_pipeline(new DrmPipeline(this)) , m_conn(drmModeGetConnector(gpu->fd(), connectorId)) { if (m_conn) { @@ -363,6 +365,11 @@ const Edid *DrmConnector::edid() const return &m_edid; } +DrmPipeline *DrmConnector::pipeline() const +{ + return m_pipeline.data(); +} + QDebug& operator<<(QDebug& s, const KWin::DrmConnector *obj) { QDebugStateSaver saver(s); diff --git a/src/backends/drm/drm_object_connector.h b/src/backends/drm/drm_object_connector.h index 7f82283104..671021dff0 100644 --- a/src/backends/drm/drm_object_connector.h +++ b/src/backends/drm/drm_object_connector.h @@ -22,6 +22,8 @@ namespace KWin { +class DrmPipeline; + class DrmConnector : public DrmObject { public: @@ -56,6 +58,7 @@ public: bool isConnected() const; bool isNonDesktop() const; bool isInternal() const; + DrmPipeline *pipeline() const; const Edid *edid() const; QString connectorName() const; @@ -83,6 +86,7 @@ public: AbstractWaylandOutput::RgbRange rgbRange() const; private: + QScopedPointer m_pipeline; DrmScopedPointer m_conn; QVector m_encoders; Edid m_edid; diff --git a/src/backends/drm/drm_output.cpp b/src/backends/drm/drm_output.cpp index 75679111ea..23ee8cc9f5 100644 --- a/src/backends/drm/drm_output.cpp +++ b/src/backends/drm/drm_output.cpp @@ -21,6 +21,7 @@ #include "renderloop_p.h" #include "screens.h" #include "session.h" +#include "waylandoutputconfig.h" // Qt #include #include @@ -34,8 +35,8 @@ namespace KWin { -DrmOutput::DrmOutput(DrmGpu *gpu, DrmPipeline *pipeline) - : DrmAbstractOutput(gpu) +DrmOutput::DrmOutput(DrmPipeline *pipeline) + : DrmAbstractOutput(pipeline->connector()->gpu()) , m_pipeline(pipeline) , m_connector(pipeline->connector()) { @@ -68,7 +69,6 @@ DrmOutput::DrmOutput(DrmGpu *gpu, DrmPipeline *pipeline) DrmOutput::~DrmOutput() { - hideCursor(); if (m_pageFlipPending) { pageFlipped(); } @@ -78,14 +78,14 @@ DrmOutput::~DrmOutput() bool DrmOutput::initCursor(const QSize &cursorSize) { m_cursor = QSharedPointer::create(m_gpu, cursorSize); - if (!m_cursor->map(QImage::Format_ARGB32_Premultiplied)) { - return false; - } - return updateCursor(); + return m_cursor->map(QImage::Format_ARGB32_Premultiplied); } bool DrmOutput::hideCursor() { + if (!isEnabled()) { + return true; + } bool visibleBefore = m_pipeline->isCursorVisible(); if (m_pipeline->setCursor(nullptr)) { if (RenderLoopPrivate::get(m_renderLoop)->presentMode == RenderLoopPrivate::SyncMode::Adaptive @@ -100,6 +100,9 @@ bool DrmOutput::hideCursor() bool DrmOutput::showCursor() { + if (!isEnabled()) { + return true; + } bool visibleBefore = m_pipeline->isCursorVisible(); const Cursor * const cursor = Cursors::self()->currentCursor(); if (m_pipeline->setCursor(m_cursor, logicalToNativeMatrix(cursor->rect(), scale(), transform()).map(cursor->hotspot()) )) { @@ -126,6 +129,9 @@ static bool isCursorSpriteCompatible(const QImage *buffer, const QImage *sprite) bool DrmOutput::updateCursor() { + if (!isEnabled()) { + return true; + } const Cursor *cursor = Cursors::self()->currentCursor(); if (!cursor) { hideCursor(); @@ -161,6 +167,9 @@ bool DrmOutput::updateCursor() bool DrmOutput::moveCursor() { + if (!isEnabled()) { + return true; + } Cursor *cursor = Cursors::self()->currentCursor(); const QMatrix4x4 hotspotMatrix = logicalToNativeMatrix(cursor->rect(), scale(), transform()); const QMatrix4x4 monitorMatrix = logicalToNativeMatrix(geometry(), scale(), transform()); @@ -222,11 +231,7 @@ void DrmOutput::initOutputDevice() void DrmOutput::updateEnablement(bool enable) { - if (m_pipeline->setActive(enable)) { - m_gpu->platform()->enableOutput(this, enable); - } else { - qCCritical(KWIN_DRM) << "failed to update enablement to" << enable; - } + m_gpu->platform()->enableOutput(this, enable); } void DrmOutput::setDpmsMode(DpmsMode mode) @@ -241,27 +246,26 @@ void DrmOutput::setDpmsMode(DpmsMode mode) } } else { m_turnOffTimer.stop(); - setDrmDpmsMode(mode); - - if (mode != dpmsMode()) { - setDpmsModeInternal(mode); + if (mode != dpmsMode() && setDrmDpmsMode(mode)) { Q_EMIT wakeUp(); } } } -void DrmOutput::setDrmDpmsMode(DpmsMode mode) +bool DrmOutput::setDrmDpmsMode(DpmsMode mode) { if (!isEnabled()) { - return; + return false; } bool active = mode == DpmsMode::On; bool isActive = dpmsMode() == DpmsMode::On; if (active == isActive) { setDpmsModeInternal(mode); - return; + return true; } - if (m_pipeline->setActive(active)) { + m_pipeline->pending.active = active; + if (DrmPipeline::commitPipelines({m_pipeline}, active ? DrmPipeline::CommitMode::Test : DrmPipeline::CommitMode::Commit)) { + m_pipeline->applyPendingChanges(); setDpmsModeInternal(mode); if (active) { m_renderLoop->uninhibit(); @@ -273,6 +277,14 @@ void DrmOutput::setDrmDpmsMode(DpmsMode mode) m_renderLoop->inhibit(); m_gpu->platform()->createDpmsFilter(); } + return true; + } else { + qCWarning(KWIN_DRM) << "Setting dpms mode failed!"; + m_pipeline->revertPendingChanges(); + if (isEnabled() && isActive && !active) { + m_gpu->platform()->checkOutputsAreOn(); + } + return false; } } @@ -304,21 +316,21 @@ DrmPlane::Transformations outputToPlaneTransform(DrmOutput::Transform transform) void DrmOutput::updateTransform(Transform transform) { setTransformInternal(transform); - const auto planeTransform = outputToPlaneTransform(transform); static bool valid; // If not set or wrong value, assume KWIN_DRM_SW_ROTATIONS_ONLY=1 until DRM transformations are reliable static int envOnlySoftwareRotations = qEnvironmentVariableIntValue("KWIN_DRM_SW_ROTATIONS_ONLY", &valid) != 0; - if (valid && !envOnlySoftwareRotations && !m_pipeline->setTransformation(planeTransform)) { - qCDebug(KWIN_DRM) << "setting transformation to" << planeTransform << "failed!"; - // just in case, if we had any rotation before, clear it - m_pipeline->setTransformation(DrmPlane::Transformation::Rotate0); - } - - // show cursor only if is enabled, i.e if pointer device is present - if (!m_gpu->platform()->isCursorHidden() && !m_gpu->platform()->usesSoftwareCursor()) { - // the cursor might need to get rotated - showCursor(); - updateCursor(); + if (valid && !envOnlySoftwareRotations) { + m_pipeline->pending.transformation = outputToPlaneTransform(transform); + if (!DrmPipeline::commitPipelines({m_pipeline}, DrmPipeline::CommitMode::Test)) { + // just in case, if we had any rotation before, clear it + m_pipeline->pending.transformation = DrmPlane::Transformation::Rotate0; + if (DrmPipeline::commitPipelines({m_pipeline}, DrmPipeline::CommitMode::Test)) { + m_pipeline->applyPendingChanges(); + } else { + m_pipeline->revertPendingChanges(); + qCWarning(KWIN_DRM) << "Can't switch rotation back to Rotate0!"; + } + } } } @@ -340,40 +352,22 @@ void DrmOutput::updateModes() // mode changed if (mode.size != modeSize() || mode.refreshRate != refreshRate()) { - applyMode(mode.id); + m_pipeline->pending.modeIndex = mode.id; + if (DrmPipeline::commitPipelines({m_pipeline}, DrmPipeline::CommitMode::Test)) { + m_pipeline->applyPendingChanges(); + auto mode = m_pipeline->connector()->currentMode(); + setCurrentModeInternal(mode.size, mode.refreshRate); + m_renderLoop->setRefreshRate(mode.refreshRate); + } else { + qCWarning(KWIN_DRM) << "Setting changed mode failed!"; + m_pipeline->revertPendingChanges(); + } } } bool DrmOutput::needsSoftwareTransformation() const { - return m_pipeline->transformation() != outputToPlaneTransform(transform()); -} - -void DrmOutput::updateMode(const QSize &size, uint32_t refreshRate) -{ - auto conn = m_pipeline->connector(); - if (conn->currentMode().size == size && conn->currentMode().refreshRate == refreshRate) { - return; - } - // try to find a fitting mode - auto modelist = conn->modes(); - for (int i = 0; i < modelist.size(); i++) { - if (modelist[i].size == size && modelist[i].refreshRate == refreshRate) { - applyMode(i); - return; - } - } - qCWarning(KWIN_DRM, "Could not find a fitting mode with size=%dx%d and refresh rate %d for output %s", - size.width(), size.height(), refreshRate, qPrintable(name())); -} - -void DrmOutput::applyMode(int modeIndex) -{ - if (m_pipeline->modeset(modeIndex)) { - auto mode = m_pipeline->connector()->currentMode(); - AbstractWaylandOutput::setCurrentModeInternal(mode.size, mode.refreshRate); - m_renderLoop->setRefreshRate(mode.refreshRate); - } + return m_pipeline->pending.transformation != outputToPlaneTransform(transform()); } void DrmOutput::pageFlipped() @@ -389,8 +383,14 @@ bool DrmOutput::present(const QSharedPointer &buffer, QRegion damaged return false; } RenderLoopPrivate *renderLoopPrivate = RenderLoopPrivate::get(m_renderLoop); - if (!m_pipeline->setSyncMode(renderLoopPrivate->presentMode)) { - setVrrPolicy(RenderLoop::VrrPolicy::Never); + if (m_pipeline->pending.syncMode != renderLoopPrivate->presentMode) { + m_pipeline->pending.syncMode = renderLoopPrivate->presentMode; + if (DrmPipeline::commitPipelines({m_pipeline}, DrmPipeline::CommitMode::Test)) { + m_pipeline->applyPendingChanges(); + } else { + m_pipeline->revertPendingChanges(); + setVrrPolicy(RenderLoop::VrrPolicy::Never); + } } if (m_pipeline->present(buffer)) { m_pageFlipPending = true; @@ -403,27 +403,22 @@ bool DrmOutput::present(const QSharedPointer &buffer, QRegion damaged int DrmOutput::gammaRampSize() const { - return m_pipeline->crtc()->gammaRampSize(); + return m_pipeline->pending.crtc ? m_pipeline->pending.crtc->gammaRampSize() : 256; } bool DrmOutput::setGammaRamp(const GammaRamp &gamma) { - if (m_pipeline->setGammaRamp(gamma)) { + m_pipeline->pending.gamma = QSharedPointer::create(m_gpu, gamma); + if (DrmPipeline::commitPipelines({m_pipeline}, DrmPipeline::CommitMode::Test)) { + m_pipeline->applyPendingChanges(); m_renderLoop->scheduleRepaint(); return true; } else { + m_pipeline->revertPendingChanges(); return false; } } -void DrmOutput::setOverscan(uint32_t overscan) -{ - if (m_pipeline->setOverscan(overscan)) { - setOverscanInternal(overscan); - m_renderLoop->scheduleRepaint(); - } -} - DrmConnector *DrmOutput::connector() const { return m_connector; @@ -449,18 +444,54 @@ QVector DrmOutput::supportedModifiers(uint32_t drmFormat) const return m_pipeline->supportedModifiers(drmFormat); } -void DrmOutput::setRgbRange(RgbRange range) +bool DrmOutput::queueChanges(const WaylandOutputConfig &config) { - if (m_pipeline->setRgbRange(range)) { - setRgbRangeInternal(range); - m_renderLoop->scheduleRepaint(); + auto props = config.constChangeSet(this); + m_pipeline->pending.active = props->enabled; + auto modelist = m_connector->modes(); + int index = -1; + for (int i = 0; i < modelist.size(); i++) { + if (modelist[i].size == props->modeSize && modelist[i].refreshRate == props->refreshRate) { + index = i; + break; + } } + if (index == -1) { + return false; + } + m_pipeline->pending.modeIndex = index; + m_pipeline->pending.overscan = props->overscan; + m_pipeline->pending.rgbRange = props->rgbRange; + m_pipeline->pending.transformation = DrmPlane::Transformation::Rotate0; + return true; } -void DrmOutput::setPipeline(DrmPipeline *pipeline) +void DrmOutput::applyQueuedChanges(const WaylandOutputConfig &config) { - Q_ASSERT_X(!pipeline || pipeline->connector() == m_connector, "DrmOutput::setPipeline", "Pipeline with wrong connector set!"); - m_pipeline = pipeline; + Q_EMIT aboutToChange(); + m_pipeline->applyPendingChanges(); + + auto props = config.constChangeSet(this); + setEnabled(props->enabled && m_pipeline->pending.crtc); + moveTo(props->pos); + setScale(props->scale); + updateTransform(props->transform); + + m_connector->setModeIndex(m_pipeline->pending.modeIndex); + auto mode = m_connector->currentMode(); + setCurrentModeInternal(mode.size, mode.refreshRate); + m_renderLoop->setRefreshRate(mode.refreshRate); + setOverscanInternal(m_pipeline->pending.overscan); + setRgbRangeInternal(m_pipeline->pending.rgbRange); + setVrrPolicy(props->vrrPolicy); + + m_renderLoop->scheduleRepaint(); + Q_EMIT changed(); +} + +void DrmOutput::revertQueuedChanges() +{ + m_pipeline->revertPendingChanges(); } } diff --git a/src/backends/drm/drm_output.h b/src/backends/drm/drm_output.h index 465dd74838..796e31140f 100644 --- a/src/backends/drm/drm_output.h +++ b/src/backends/drm/drm_output.h @@ -51,25 +51,26 @@ public: DrmConnector *connector() const; DrmPipeline *pipeline() const; - void setPipeline(DrmPipeline *pipeline); QSize sourceSize() const override; bool isFormatSupported(uint32_t drmFormat) const override; QVector supportedModifiers(uint32_t drmFormat) const override; bool needsSoftwareTransformation() const override; + bool queueChanges(const WaylandOutputConfig &config); + void applyQueuedChanges(const WaylandOutputConfig &config); + void revertQueuedChanges(); + private: friend class DrmGpu; friend class DrmBackend; - DrmOutput(DrmGpu* gpu, DrmPipeline *pipeline); + DrmOutput(DrmPipeline *pipeline); void initOutputDevice(); void updateEnablement(bool enable) override; - void setDrmDpmsMode(DpmsMode mode); + bool setDrmDpmsMode(DpmsMode mode); void setDpmsMode(DpmsMode mode) override; - void applyMode(int modeIndex) override; - void updateMode(const QSize &size, uint32_t refreshRate) override; void updateModes(); QVector getModes() const; @@ -78,8 +79,6 @@ private: int gammaRampSize() const override; bool setGammaRamp(const GammaRamp &gamma) override; - void setOverscan(uint32_t overscan) override; - void setRgbRange(RgbRange range) override; DrmPipeline *m_pipeline; DrmConnector *m_connector; diff --git a/src/backends/drm/drm_pipeline.cpp b/src/backends/drm/drm_pipeline.cpp index 8b5bbdaf1e..2905645210 100644 --- a/src/backends/drm/drm_pipeline.cpp +++ b/src/backends/drm/drm_pipeline.cpp @@ -34,16 +34,13 @@ namespace KWin { -DrmPipeline::DrmPipeline(DrmGpu *gpu, DrmConnector *conn, DrmCrtc *crtc) +DrmPipeline::DrmPipeline(DrmConnector *conn) : m_output(nullptr) - , m_gpu(gpu) , m_connector(conn) - , m_crtc(crtc) - , m_primaryPlane(crtc->primaryPlane()) { - m_allObjects << m_connector << m_crtc; - if (m_primaryPlane) { - m_allObjects << m_primaryPlane; + if (!gpu()->atomicModeSetting()) { + m_formats.insert(DRM_FORMAT_XRGB8888, {}); + m_formats.insert(DRM_FORMAT_ARGB8888, {}); } } @@ -51,47 +48,22 @@ DrmPipeline::~DrmPipeline() { } -void DrmPipeline::setup() -{ - if (m_gpu->atomicModeSetting()) { - if (m_connector->getProp(DrmConnector::PropertyIndex::CrtcId)->current() == m_crtc->id()) { - m_connector->findCurrentMode(m_crtc->queryCurrentMode()); - } - m_connector->setPending(DrmConnector::PropertyIndex::CrtcId, m_crtc->id()); - m_crtc->setPending(DrmCrtc::PropertyIndex::Active, 1); - auto mode = m_connector->currentMode(); - m_crtc->setPendingBlob(DrmCrtc::PropertyIndex::ModeId, &mode.mode, sizeof(drmModeModeInfo)); - m_primaryPlane->setPending(DrmPlane::PropertyIndex::CrtcId, m_crtc->id()); - m_primaryPlane->set(QPoint(0, 0), sourceSize(), QPoint(0, 0), mode.size); - m_primaryPlane->setTransformation(DrmPlane::Transformation::Rotate0); - m_formats = m_primaryPlane->formats(); - } else { - m_formats.insert(DRM_FORMAT_XRGB8888, {}); - m_formats.insert(DRM_FORMAT_ARGB8888, {}); - } -} - -bool DrmPipeline::test() -{ - return checkTestBuffer() && commitPipelines(m_gpu->pipelines(), CommitMode::Test); -} - bool DrmPipeline::present(const QSharedPointer &buffer) { + Q_ASSERT(pending.crtc); m_primaryBuffer = buffer; - if (m_gpu->useEglStreams() && m_gpu->eglBackend() != nullptr && m_gpu == m_gpu->platform()->primaryGpu()) { + if (gpu()->useEglStreams() && gpu()->eglBackend() != nullptr && gpu() == gpu()->platform()->primaryGpu()) { // EglStreamBackend queues normal page flips through EGL, // modesets etc are performed through DRM-KMS - bool needsCommit = std::any_of(m_allObjects.constBegin(), m_allObjects.constEnd(), [](auto obj){return obj->needsCommit();}); - if (!needsCommit) { + if (!m_connector->needsCommit() && !pending.crtc->needsCommit()) { return true; } } - if (m_gpu->atomicModeSetting()) { - if (!atomicCommit()) { + if (gpu()->atomicModeSetting()) { + if (!commitPipelines({this}, CommitMode::Commit)) { // update properties and try again updateProperties(); - if (!atomicCommit()) { + if (!commitPipelines({this}, CommitMode::Commit)) { qCWarning(KWIN_DRM) << "Atomic present failed!" << strerror(errno); printDebugInfo(); return false; @@ -106,22 +78,16 @@ bool DrmPipeline::present(const QSharedPointer &buffer) return true; } -bool DrmPipeline::atomicCommit() -{ - return commitPipelines({this}, CommitMode::Commit); -} - bool DrmPipeline::commitPipelines(const QVector &pipelines, CommitMode mode) { Q_ASSERT(!pipelines.isEmpty()); - - if (pipelines[0]->m_gpu->atomicModeSetting()) { + if (pipelines[0]->gpu()->atomicModeSetting()) { drmModeAtomicReq *req = drmModeAtomicAlloc(); if (!req) { qCDebug(KWIN_DRM) << "Failed to allocate drmModeAtomicReq!" << strerror(errno); return false; } - uint32_t flags = 0; + uint32_t flags = DRM_MODE_ATOMIC_NONBLOCK; const auto &failed = [pipelines, req](){ drmModeAtomicFree(req); for (const auto &pipeline : pipelines) { @@ -130,8 +96,10 @@ bool DrmPipeline::commitPipelines(const QVector &pipelines, Commit pipeline->m_primaryBuffer = pipeline->m_oldTestBuffer; pipeline->m_oldTestBuffer = nullptr; } - for (const auto &obj : qAsConst(pipeline->m_allObjects)) { - obj->rollbackPending(); + pipeline->m_connector->rollbackPending(); + if (pipeline->pending.crtc) { + pipeline->pending.crtc->rollbackPending(); + pipeline->pending.crtc->primaryPlane()->rollbackPending(); } } return false; @@ -146,131 +114,103 @@ bool DrmPipeline::commitPipelines(const QVector &pipelines, Commit return failed(); } } - if (drmModeAtomicCommit(pipelines[0]->m_gpu->fd(), req, (flags & (~DRM_MODE_PAGE_FLIP_EVENT)) | DRM_MODE_ATOMIC_TEST_ONLY, pipelines[0]->output()) != 0) { + if (drmModeAtomicCommit(pipelines[0]->gpu()->fd(), req, (flags & (~DRM_MODE_PAGE_FLIP_EVENT)) | DRM_MODE_ATOMIC_TEST_ONLY, pipelines[0]->output()) != 0) { qCWarning(KWIN_DRM) << "Atomic test for" << mode << "failed!" << strerror(errno); return failed(); } - if (mode != CommitMode::Test && drmModeAtomicCommit(pipelines[0]->m_gpu->fd(), req, flags, pipelines[0]->output()) != 0) { + if (mode != CommitMode::Test && drmModeAtomicCommit(pipelines[0]->gpu()->fd(), req, flags, pipelines[0]->output()) != 0) { qCWarning(KWIN_DRM) << "Atomic commit failed! This should never happen!" << strerror(errno); return failed(); } for (const auto &pipeline : pipelines) { pipeline->m_oldTestBuffer = nullptr; - for (const auto &obj : qAsConst(pipeline->m_allObjects)) { - obj->commitPending(); + pipeline->m_connector->commitPending(); + if (pipeline->pending.crtc) { + pipeline->pending.crtc->commitPending(); + pipeline->pending.crtc->primaryPlane()->commitPending(); } if (mode != CommitMode::Test) { - pipeline->m_primaryPlane->setNext(pipeline->m_primaryBuffer); - for (const auto &obj : qAsConst(pipeline->m_allObjects)) { - obj->commit(); + pipeline->m_connector->commit(); + if (pipeline->pending.crtc) { + pipeline->pending.crtc->primaryPlane()->setNext(pipeline->m_primaryBuffer); + pipeline->pending.crtc->commit(); + pipeline->pending.crtc->primaryPlane()->commit(); } + pipeline->m_current = pipeline->pending; } } drmModeAtomicFree(req); return true; } else { + bool failure = false; for (const auto &pipeline : pipelines) { - if (pipeline->m_legacyNeedsModeset && pipeline->isActive() && !pipeline->modeset(0)) { - return false; + if (!pipeline->applyPendingChangesLegacy()) { + failure = true; + break; } } - return true; + if (failure) { + // at least try to revert the config + for (const auto &pipeline : pipelines) { + pipeline->pending = pipeline->m_next; + pipeline->applyPendingChangesLegacy(); + } + return false; + } else { + return true; + } } } bool DrmPipeline::populateAtomicValues(drmModeAtomicReq *req, uint32_t &flags) { - bool usesEglStreams = m_gpu->useEglStreams() && m_gpu->eglBackend() != nullptr && m_gpu == m_gpu->platform()->primaryGpu(); - if (!usesEglStreams && isActive()) { + if (needsModeset()) { + prepareModeset(); + flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; + } + bool usesEglStreams = gpu()->useEglStreams() && gpu()->eglBackend() != nullptr && gpu() == gpu()->platform()->primaryGpu(); + if (!usesEglStreams && activePending()) { flags |= DRM_MODE_PAGE_FLIP_EVENT; } - bool needsModeset = std::any_of(m_allObjects.constBegin(), m_allObjects.constEnd(), [](auto obj){return obj->needsModeset();}); - if (needsModeset) { - flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; - } else { - flags |= DRM_MODE_ATOMIC_NONBLOCK; - } m_lastFlags = flags; - - auto modeSize = m_connector->currentMode().size; - m_primaryPlane->set(QPoint(0, 0), m_primaryBuffer ? m_primaryBuffer->size() : modeSize, QPoint(0, 0), modeSize); - m_primaryPlane->setBuffer(isActive() ? m_primaryBuffer.get() : nullptr); - for (const auto &obj : qAsConst(m_allObjects)) { - if (!obj->atomicPopulate(req)) { - return false; + if (pending.crtc) { + auto modeSize = m_connector->modes()[pending.modeIndex].size; + pending.crtc->primaryPlane()->set(QPoint(0, 0), m_primaryBuffer ? m_primaryBuffer->size() : modeSize, QPoint(0, 0), modeSize); + pending.crtc->primaryPlane()->setBuffer(activePending() ? m_primaryBuffer.get() : nullptr); + pending.crtc->setPending(DrmCrtc::PropertyIndex::VrrEnabled, pending.syncMode == RenderLoopPrivate::SyncMode::Adaptive); + if (pending.gamma != m_current.gamma) { + if (pending.gamma) { + pending.crtc->setPendingBlob(DrmCrtc::PropertyIndex::Gamma_LUT, pending.gamma->atomicLut, pending.gamma->size * sizeof(drm_color_lut)); + } else { + pending.crtc->setPendingBlob(DrmCrtc::PropertyIndex::Gamma_LUT, nullptr, 256 * sizeof(drm_color_lut)); + } } } - return true; + return m_connector->atomicPopulate(req) && (!pending.crtc || (pending.crtc->atomicPopulate(req) && pending.crtc->primaryPlane()->atomicPopulate(req))); } bool DrmPipeline::presentLegacy() { - if ((!m_crtc->current() || m_crtc->current()->needsModeChange(m_primaryBuffer.get())) && !modeset(m_connector->currentModeIndex())) { + if ((!pending.crtc->current() || pending.crtc->current()->needsModeChange(m_primaryBuffer.get())) && !legacyModeset()) { return false; } m_lastFlags = DRM_MODE_PAGE_FLIP_EVENT; - m_crtc->setNext(m_primaryBuffer); - if (drmModePageFlip(m_gpu->fd(), m_crtc->id(), m_primaryBuffer ? m_primaryBuffer->bufferId() : 0, DRM_MODE_PAGE_FLIP_EVENT, m_output) != 0) { + QVector *userData = new QVector(); + *userData << this; + if (drmModePageFlip(gpu()->fd(), pending.crtc->id(), m_primaryBuffer ? m_primaryBuffer->bufferId() : 0, DRM_MODE_PAGE_FLIP_EVENT, userData) != 0) { qCWarning(KWIN_DRM) << "Page flip failed:" << strerror(errno) << m_primaryBuffer; return false; } - return true; -} - -bool DrmPipeline::modeset(int modeIndex) -{ - int oldModeIndex = m_connector->currentModeIndex(); - m_connector->setModeIndex(modeIndex); - auto mode = m_connector->currentMode().mode; - if (m_gpu->atomicModeSetting()) { - m_crtc->setPendingBlob(DrmCrtc::PropertyIndex::ModeId, &mode, sizeof(drmModeModeInfo)); - if (m_connector->hasOverscan()) { - m_connector->setOverscan(m_connector->overscan(), m_connector->currentMode().size); - } - bool works = test(); - // hardware rotation could fail in some modes, try again with soft rotation if possible - if (!works - && transformation() != DrmPlane::Transformations(DrmPlane::Transformation::Rotate0) - && setPendingTransformation(DrmPlane::Transformation::Rotate0)) { - // values are reset on the failing test, set them again - m_crtc->setPendingBlob(DrmCrtc::PropertyIndex::ModeId, &mode, sizeof(drmModeModeInfo)); - if (m_connector->hasOverscan()) { - m_connector->setOverscan(m_connector->overscan(), m_connector->currentMode().size); - } - works = test(); - } - if (!works) { - qCWarning(KWIN_DRM) << "Modeset failed!" << strerror(errno); - m_connector->setModeIndex(oldModeIndex); - return false; - } - } else { - uint32_t connId = m_connector->id(); - if (!checkTestBuffer() || drmModeSetCrtc(m_gpu->fd(), m_crtc->id(), m_primaryBuffer->bufferId(), 0, 0, &connId, 1, &mode) != 0) { - qCWarning(KWIN_DRM) << "Modeset failed!" << strerror(errno); - m_connector->setModeIndex(oldModeIndex); - m_primaryBuffer = m_oldTestBuffer; - return false; - } - m_oldTestBuffer = nullptr; - m_legacyNeedsModeset = false; - // make sure the buffer gets kept alive, or the modeset gets reverted by the kernel - if (m_crtc->current()) { - m_crtc->setNext(m_primaryBuffer); - } else { - m_crtc->setCurrent(m_primaryBuffer); - } - m_connector->getProp(DrmConnector::PropertyIndex::Dpms)->setCurrent(DRM_MODE_DPMS_ON); - } + pending.crtc->setNext(m_primaryBuffer); return true; } bool DrmPipeline::checkTestBuffer() { - if (m_primaryBuffer && m_primaryBuffer->size() == sourceSize()) { + if (!pending.crtc || (m_primaryBuffer && m_primaryBuffer->size() == sourceSize())) { return true; } - auto backend = m_gpu->eglBackend(); + auto backend = gpu()->eglBackend(); QSharedPointer buffer; // try to re-use buffers if possible. const auto &checkBuffer = [this, backend, &buffer](const QSharedPointer &buf){ @@ -281,29 +221,29 @@ bool DrmPipeline::checkTestBuffer() buffer = buf; } }; - if (m_primaryPlane && m_primaryPlane->next()) { - checkBuffer(m_primaryPlane->next()); - } else if (m_primaryPlane && m_primaryPlane->current()) { - checkBuffer(m_primaryPlane->current()); - } else if (m_crtc->next()) { - checkBuffer(m_crtc->next()); - } else if (m_crtc->current()) { - checkBuffer(m_crtc->current()); + if (pending.crtc->primaryPlane() && pending.crtc->primaryPlane()->next()) { + checkBuffer(pending.crtc->primaryPlane()->next()); + } else if (pending.crtc->primaryPlane() && pending.crtc->primaryPlane()->current()) { + checkBuffer(pending.crtc->primaryPlane()->current()); + } else if (pending.crtc->next()) { + checkBuffer(pending.crtc->next()); + } else if (pending.crtc->current()) { + checkBuffer(pending.crtc->current()); } // if we don't have a fitting buffer already, get or create one if (buffer) { #if HAVE_GBM } else if (backend && m_output) { buffer = backend->renderTestFrame(m_output); - } else if (backend && m_gpu->gbmDevice()) { - gbm_bo *bo = gbm_bo_create(m_gpu->gbmDevice(), sourceSize().width(), sourceSize().height(), GBM_FORMAT_XRGB8888, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); + } else if (backend && gpu()->gbmDevice()) { + gbm_bo *bo = gbm_bo_create(gpu()->gbmDevice(), sourceSize().width(), sourceSize().height(), GBM_FORMAT_XRGB8888, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); if (!bo) { return false; } - buffer = QSharedPointer::create(m_gpu, bo, nullptr); + buffer = QSharedPointer::create(gpu(), bo, nullptr); #endif } else { - buffer = QSharedPointer::create(m_gpu, sourceSize()); + buffer = QSharedPointer::create(gpu(), sourceSize()); } if (buffer && buffer->bufferId()) { m_oldTestBuffer = m_primaryBuffer; @@ -315,175 +255,117 @@ bool DrmPipeline::checkTestBuffer() bool DrmPipeline::setCursor(const QSharedPointer &buffer, const QPoint &hotspot) { - return m_crtc->setLegacyCursor(buffer, hotspot); + return pending.crtc->setLegacyCursor(buffer, hotspot); } bool DrmPipeline::moveCursor(QPoint pos) { - return m_crtc->moveLegacyCursor(pos); + return pending.crtc->moveLegacyCursor(pos); } -bool DrmPipeline::setActive(bool active) +void DrmPipeline::prepareModeset() { - // disable the cursor before the primary plane to circumvent a crash in amdgpu - if (isActive() && !active) { - if (drmModeSetCursor(m_gpu->fd(), m_crtc->id(), 0, 0, 0) != 0) { - qCWarning(KWIN_DRM) << "Could not set cursor:" << strerror(errno); - } + if (!pending.crtc) { + m_connector->setPending(DrmConnector::PropertyIndex::CrtcId, 0); + return; } - bool success = false; - auto mode = m_connector->currentMode().mode; - if (m_gpu->atomicModeSetting()) { - m_connector->setPending(DrmConnector::PropertyIndex::CrtcId, active ? m_crtc->id() : 0); - m_crtc->setPending(DrmCrtc::PropertyIndex::Active, active); - m_crtc->setPendingBlob(DrmCrtc::PropertyIndex::ModeId, active ? &mode : nullptr, sizeof(drmModeModeInfo)); - m_primaryPlane->setPending(DrmPlane::PropertyIndex::CrtcId, active ? m_crtc->id() : 0); - if (active) { - success = test(); - if (!success) { - updateProperties(); - success = test(); - } - } else { - // immediately commit if disabling as there will be no present - success = atomicCommit(); - } - } else { - success = modeset(m_connector->currentModeIndex()); - if (success) { - success = m_connector->getProp(DrmConnector::PropertyIndex::Dpms)->setPropertyLegacy(active ? DRM_MODE_DPMS_ON : DRM_MODE_DPMS_OFF); - } + auto mode = m_connector->modes()[pending.modeIndex]; + + m_connector->setPending(DrmConnector::PropertyIndex::CrtcId, activePending() ? pending.crtc->id() : 0); + if (const auto &prop = m_connector->getProp(DrmConnector::PropertyIndex::Broadcast_RGB)) { + prop->setEnum(pending.rgbRange); } - if (!success) { - qCWarning(KWIN_DRM) << "Setting active to" << active << "failed" << strerror(errno); - } - if (isActive()) { - // enable cursor (again) - m_crtc->setLegacyCursor(); - } - return success; + + pending.crtc->setPending(DrmCrtc::PropertyIndex::Active, activePending()); + pending.crtc->setPendingBlob(DrmCrtc::PropertyIndex::ModeId, activePending() ? &mode.mode : nullptr, sizeof(drmModeModeInfo)); + + pending.crtc->primaryPlane()->setPending(DrmPlane::PropertyIndex::CrtcId, activePending() ? pending.crtc->id() : 0); + pending.crtc->primaryPlane()->setTransformation(DrmPlane::Transformation::Rotate0); + pending.crtc->primaryPlane()->set(QPoint(0, 0), sourceSize(), QPoint(0, 0), mode.size); + + m_formats = pending.crtc->primaryPlane()->formats(); } -bool DrmPipeline::setGammaRamp(const GammaRamp &ramp) +void DrmPipeline::applyPendingChanges() { - // There are old Intel iGPUs that don't have full support for setting - // the gamma ramp with AMS -> fall back to legacy without the property - if (m_gpu->atomicModeSetting() && m_crtc->getProp(DrmCrtc::PropertyIndex::Gamma_LUT)) { - 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 = m_crtc->setPendingBlob(DrmCrtc::PropertyIndex::Gamma_LUT, gamma, ramp.size() * sizeof(drm_color_lut)); - delete[] gamma; - if (!result) { - qCWarning(KWIN_DRM) << "Could not create gamma LUT property blob" << strerror(errno); + if (!pending.crtc) { + pending.active = false; + } + m_next = pending; +} + +bool DrmPipeline::applyPendingChangesLegacy() +{ + if (!pending.active && pending.crtc) { + drmModeSetCursor(gpu()->fd(), pending.crtc->id(), 0, 0, 0); + } + if (pending.active) { + Q_ASSERT(pending.crtc); + if (auto vrr = pending.crtc->getProp(DrmCrtc::PropertyIndex::VrrEnabled); !vrr->setPropertyLegacy(pending.syncMode == RenderLoopPrivate::SyncMode::Adaptive)) { + qCWarning(KWIN_DRM) << "Setting vrr failed!" << strerror(errno); return false; } - if (!test()) { + if (const auto &rgbRange = m_connector->getProp(DrmConnector::PropertyIndex::Broadcast_RGB)) { + rgbRange->setEnumLegacy(pending.rgbRange); + } + if (needsModeset() &&!legacyModeset()) { + return false; + } + m_connector->getProp(DrmConnector::PropertyIndex::Dpms)->setCurrent(DRM_MODE_DPMS_ON); + if (pending.gamma && drmModeCrtcSetGamma(gpu()->fd(), pending.crtc->id(), pending.gamma->size, + const_cast(pending.gamma->lut.red()), + const_cast(pending.gamma->lut.blue()), + const_cast(pending.gamma->lut.green())) != 0) { qCWarning(KWIN_DRM) << "Setting gamma failed!" << strerror(errno); return false; } - } else { - uint16_t *red = const_cast(ramp.red()); - uint16_t *green = const_cast(ramp.green()); - uint16_t *blue = const_cast(ramp.blue()); - if (drmModeCrtcSetGamma(m_gpu->fd(), m_crtc->id(), ramp.size(), red, green, blue) != 0) { - qCWarning(KWIN_DRM) << "setting gamma failed!" << strerror(errno); - return false; - } - } - return true; -} -bool DrmPipeline::setTransformation(const DrmPlane::Transformations &transformation) -{ - return setPendingTransformation(transformation) && test(); -} - -bool DrmPipeline::setPendingTransformation(const DrmPlane::Transformations &transformation) -{ - if (this->transformation() == transformation) { - return true; + pending.crtc->setLegacyCursor(); } - if (!m_gpu->atomicModeSetting()) { - return false; - } - if (!m_primaryPlane->setTransformation(transformation)) { + if (!m_connector->getProp(DrmConnector::PropertyIndex::Dpms)->setPropertyLegacy(pending.active ? DRM_MODE_DPMS_ON : DRM_MODE_DPMS_OFF)) { + qCWarning(KWIN_DRM) << "Setting legacy dpms failed!" << strerror(errno); return false; } return true; } -bool DrmPipeline::setSyncMode(RenderLoopPrivate::SyncMode syncMode) +bool DrmPipeline::legacyModeset() { - auto vrrProp = m_crtc->getProp(DrmCrtc::PropertyIndex::VrrEnabled); - if (!vrrProp || !m_connector->vrrCapable()) { - return syncMode == RenderLoopPrivate::SyncMode::Fixed; - } - bool vrr = syncMode == RenderLoopPrivate::SyncMode::Adaptive; - if (vrrProp->pending() == vrr) { - return true; - } - if (m_gpu->atomicModeSetting()) { - vrrProp->setPending(vrr); - return test(); - } else { - return vrrProp->setPropertyLegacy(vrr); - } -} - -bool DrmPipeline::setOverscan(uint32_t overscan) -{ - if (overscan > 100 || (overscan != 0 && !m_connector->hasOverscan())) { + auto mode = m_connector->modes()[pending.modeIndex]; + uint32_t connId = m_connector->id(); + if (!checkTestBuffer() || drmModeSetCrtc(gpu()->fd(), pending.crtc->id(), m_primaryBuffer->bufferId(), 0, 0, &connId, 1, &mode.mode) != 0) { + qCWarning(KWIN_DRM) << "Modeset failed!" << strerror(errno); + pending = m_next; + m_primaryBuffer = m_oldTestBuffer; return false; } - m_connector->setOverscan(overscan, m_connector->currentMode().size); - return test(); -} - -bool DrmPipeline::setRgbRange(AbstractWaylandOutput::RgbRange rgbRange) -{ - if (const auto &prop = m_connector->getProp(DrmConnector::PropertyIndex::Broadcast_RGB)) { - prop->setEnum(rgbRange); - return test(); + m_oldTestBuffer = nullptr; + // make sure the buffer gets kept alive, or the modeset gets reverted by the kernel + if (pending.crtc->current()) { + pending.crtc->setNext(m_primaryBuffer); } else { - return false; + pending.crtc->setCurrent(m_primaryBuffer); } + return true; } QSize DrmPipeline::sourceSize() const { - auto mode = m_connector->currentMode(); - if (transformation() & (DrmPlane::Transformation::Rotate90 | DrmPlane::Transformation::Rotate270)) { + auto mode = m_connector->modes()[pending.modeIndex]; + if (pending.transformation & (DrmPlane::Transformation::Rotate90 | DrmPlane::Transformation::Rotate270)) { return mode.size.transposed(); } return mode.size; } -DrmPlane::Transformations DrmPipeline::transformation() const -{ - return m_primaryPlane ? m_primaryPlane->transformation() : DrmPlane::Transformation::Rotate0; -} - -bool DrmPipeline::isActive() const -{ - if (m_gpu->atomicModeSetting()) { - return m_crtc->getProp(DrmCrtc::PropertyIndex::Active)->pending() != 0; - } else { - return m_connector->getProp(DrmConnector::PropertyIndex::Dpms)->current() == DRM_MODE_DPMS_ON; - } -} - bool DrmPipeline::isCursorVisible() const { - return m_crtc->isCursorVisible(QRect(QPoint(0, 0), m_connector->currentMode().size)); + return pending.crtc && pending.crtc->isCursorVisible(QRect(QPoint(0, 0), m_connector->currentMode().size)); } QPoint DrmPipeline::cursorPos() const { - return m_crtc->cursorPos(); + return pending.crtc->cursorPos(); } DrmConnector *DrmPipeline::connector() const @@ -491,21 +373,16 @@ DrmConnector *DrmPipeline::connector() const return m_connector; } -DrmCrtc *DrmPipeline::crtc() const +DrmGpu *DrmPipeline::gpu() const { - return m_crtc; -} - -DrmPlane *DrmPipeline::primaryPlane() const -{ - return m_primaryPlane; + return m_connector->gpu(); } void DrmPipeline::pageFlipped() { - m_crtc->flipBuffer(); - if (m_primaryPlane) { - m_primaryPlane->flipBuffer(); + m_current.crtc->flipBuffer(); + if (m_current.crtc->primaryPlane()) { + m_current.crtc->primaryPlane()->flipBuffer(); } } @@ -521,12 +398,16 @@ DrmOutput *DrmPipeline::output() const void DrmPipeline::updateProperties() { - for (const auto &obj : qAsConst(m_allObjects)) { - obj->updateProperties(); + m_connector->updateProperties(); + if (pending.crtc) { + pending.crtc->updateProperties(); + if (pending.crtc->primaryPlane()) { + pending.crtc->primaryPlane()->updateProperties(); + } } // with legacy we don't know what happened to the cursor after VT switch // so make sure it gets set again - m_crtc->setLegacyCursor(); + pending.crtc->setLegacyCursor(); } bool DrmPipeline::isFormatSupported(uint32_t drmFormat) const @@ -539,6 +420,44 @@ QVector DrmPipeline::supportedModifiers(uint32_t drmFormat) const return m_formats[drmFormat]; } +bool DrmPipeline::needsModeset() const +{ + return pending.crtc != m_current.crtc + || pending.active != m_current.active + || pending.modeIndex != m_current.modeIndex + || pending.rgbRange != m_current.rgbRange + || pending.transformation != m_current.transformation; +} + +bool DrmPipeline::activePending() const +{ + return pending.crtc && pending.active; +} + +void DrmPipeline::revertPendingChanges() +{ + pending = m_next; +} + +DrmGammaRamp::DrmGammaRamp(DrmGpu *gpu, const GammaRamp &lut) + : lut(lut) + , size(lut.size()) +{ + if (gpu->atomicModeSetting()) { + atomicLut = new drm_color_lut[size]; + for (uint32_t i = 0; i < size; i++) { + atomicLut[i].red = lut.red()[i]; + atomicLut[i].green = lut.green()[i]; + atomicLut[i].blue = lut.blue()[i]; + } + } +} + +DrmGammaRamp::~DrmGammaRamp() +{ + delete[] atomicLut; +} + static void printProps(DrmObject *object) { auto list = object->properties(); @@ -574,11 +493,13 @@ void DrmPipeline::printDebugInfo() const qCWarning(KWIN_DRM) << "Drm objects:"; qCWarning(KWIN_DRM) << "connector" << m_connector->id(); printProps(m_connector); - qCWarning(KWIN_DRM) << "crtc" << m_crtc->id(); - printProps(m_crtc); - if (m_primaryPlane) { - qCWarning(KWIN_DRM) << "primary plane" << m_primaryPlane->id(); - printProps(m_primaryPlane); + if (pending.crtc) { + qCWarning(KWIN_DRM) << "crtc" << pending.crtc->id(); + printProps(pending.crtc); + if (pending.crtc->primaryPlane()) { + qCWarning(KWIN_DRM) << "primary plane" << pending.crtc->primaryPlane()->id(); + printProps(pending.crtc->primaryPlane()); + } } } diff --git a/src/backends/drm/drm_pipeline.h b/src/backends/drm/drm_pipeline.h index e4e07be1c9..b2ea09e264 100644 --- a/src/backends/drm/drm_pipeline.h +++ b/src/backends/drm/drm_pipeline.h @@ -30,40 +30,42 @@ class DrmBuffer; class DrmDumbBuffer; class GammaRamp; +class DrmGammaRamp +{ +public: + DrmGammaRamp(DrmGpu *gpu, const GammaRamp &lut); + ~DrmGammaRamp(); + + const GammaRamp lut; + drm_color_lut *atomicLut = nullptr; + uint32_t size; +}; + class DrmPipeline { public: - DrmPipeline(DrmGpu *gpu, DrmConnector *conn, DrmCrtc *crtc); + DrmPipeline(DrmConnector *conn); ~DrmPipeline(); - /** - * Sets the necessary initial drm properties for the pipeline to work - */ - void setup(); - /** * 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); - bool modeset(int modeIndex); - bool setCursor(const QSharedPointer &buffer, const QPoint &hotspot = QPoint()); - bool setActive(bool enable); - bool setGammaRamp(const GammaRamp &ramp); - bool setTransformation(const DrmPlane::Transformations &transform); - bool moveCursor(QPoint pos); - bool setSyncMode(RenderLoopPrivate::SyncMode syncMode); - bool setOverscan(uint32_t overscan); - bool setRgbRange(AbstractWaylandOutput::RgbRange rgbRange); + bool needsModeset() const; + void prepareModeset(); + void applyPendingChanges(); + void revertPendingChanges(); + + bool setCursor(const QSharedPointer &buffer, const QPoint &hotspot = QPoint()); + bool moveCursor(QPoint pos); - DrmPlane::Transformations transformation() const; bool isCursorVisible() const; QPoint cursorPos() const; DrmConnector *connector() const; - DrmCrtc *crtc() const; - DrmPlane *primaryPlane() const; + DrmGpu *gpu() const; void pageFlipped(); void printDebugInfo() const; @@ -76,6 +78,18 @@ public: void setOutput(DrmOutput *output); DrmOutput *output() const; + struct State { + DrmCrtc *crtc = nullptr; + bool active = true; + int modeIndex = 0; + uint32_t overscan = 0; + AbstractWaylandOutput::RgbRange rgbRange = AbstractWaylandOutput::RgbRange::Automatic; + RenderLoopPrivate::SyncMode syncMode = RenderLoopPrivate::SyncMode::Fixed; + QSharedPointer gamma; + DrmPlane::Transformations transformation = DrmPlane::Transformation::Rotate0; + }; + State pending; + enum class CommitMode { Test, Commit @@ -85,29 +99,26 @@ public: private: bool populateAtomicValues(drmModeAtomicReq *req, uint32_t &flags); - bool test(); - bool atomicCommit(); bool presentLegacy(); bool checkTestBuffer(); - bool isActive() const; + bool activePending() const; - bool setPendingTransformation(const DrmPlane::Transformations &transformation); + bool applyPendingChangesLegacy(); + bool legacyModeset(); DrmOutput *m_output = nullptr; - DrmGpu *m_gpu = nullptr; DrmConnector *m_connector = nullptr; - DrmCrtc *m_crtc = nullptr; - DrmPlane *m_primaryPlane = nullptr; QSharedPointer m_primaryBuffer; QSharedPointer m_oldTestBuffer; - bool m_legacyNeedsModeset = true; - - QVector m_allObjects; QMap> m_formats; - int m_lastFlags = 0; + + // the state that will be applied at the next real atomic commit + State m_next; + // the state that is already committed + State m_current; }; } diff --git a/src/backends/drm/drm_property.h b/src/backends/drm/drm_property.h index b0411e8be9..281446dd44 100644 --- a/src/backends/drm/drm_property.h +++ b/src/backends/drm/drm_property.h @@ -70,6 +70,13 @@ public: bool needsCommit() const; bool setPropertyLegacy(uint64_t value); + template + bool setEnumLegacy(T value) { + if (hasEnum(static_cast(value))) { + return setPropertyLegacy(m_enumMap[static_cast(value)]); + } + return false; + } private: uint32_t m_propId = 0; diff --git a/src/backends/drm/drm_virtual_output.cpp b/src/backends/drm/drm_virtual_output.cpp index fc5e3f0c82..dd98723266 100644 --- a/src/backends/drm/drm_virtual_output.cpp +++ b/src/backends/drm/drm_virtual_output.cpp @@ -65,17 +65,6 @@ void DrmVirtualOutput::vblank(std::chrono::nanoseconds timestamp) } } -void DrmVirtualOutput::applyMode(int modeIndex) -{ - Q_UNUSED(modeIndex) -} - -void DrmVirtualOutput::updateMode(const QSize &size, uint32_t refreshRate) -{ - Q_UNUSED(size) - Q_UNUSED(refreshRate) -} - void DrmVirtualOutput::setDpmsMode(DpmsMode mode) { setDpmsModeInternal(mode); diff --git a/src/backends/drm/drm_virtual_output.h b/src/backends/drm/drm_virtual_output.h index f208776460..160999bb6d 100644 --- a/src/backends/drm/drm_virtual_output.h +++ b/src/backends/drm/drm_virtual_output.h @@ -47,8 +47,6 @@ public: private: void vblank(std::chrono::nanoseconds timestamp); - void applyMode(int modeIndex) override; - void updateMode(const QSize &size, uint32_t refreshRate) override; void setDpmsMode(DpmsMode mode) override; void updateEnablement(bool enable) override; diff --git a/src/backends/drm/egl_stream_backend.cpp b/src/backends/drm/egl_stream_backend.cpp index 616488d154..1015c1e095 100644 --- a/src/backends/drm/egl_stream_backend.cpp +++ b/src/backends/drm/egl_stream_backend.cpp @@ -307,7 +307,7 @@ bool EglStreamBackend::resetOutput(Output &o) if (isPrimary()) { // dumb buffer used for modesetting o.buffer = QSharedPointer::create(m_gpu, sourceSize); - o.targetPlane = drmOutput->pipeline()->primaryPlane(); + o.targetPlane = drmOutput->pipeline()->pending.crtc->primaryPlane(); EGLAttrib streamAttribs[] = { EGL_STREAM_FIFO_LENGTH_KHR, 0, // mailbox mode @@ -476,7 +476,7 @@ SurfaceTexture *EglStreamBackend::createSurfaceTextureWayland(SurfacePixmapWayla bool EglStreamBackend::needsReset(const Output &o) const { - if (o.targetPlane != o.output->pipeline()->primaryPlane()) { + if (o.targetPlane != o.output->pipeline()->pending.crtc->primaryPlane()) { return true; } QSize surfaceSize = o.dumbSwapchain ? o.dumbSwapchain->size() : o.buffer->size(); diff --git a/src/platform.cpp b/src/platform.cpp index 8dc8790a49..aad545be93 100644 --- a/src/platform.cpp +++ b/src/platform.cpp @@ -23,6 +23,7 @@ #include "screenedge.h" #include "touch_input.h" #include "wayland_server.h" +#include "waylandoutputconfig.h" #include #include @@ -120,55 +121,61 @@ void Platform::requestOutputsChange(KWaylandServer::OutputConfigurationV2Interfa return; } + WaylandOutputConfig cfg; const auto changes = config->changes(); - - //process all non-disabling changes for (auto it = changes.begin(); it != changes.end(); it++) { const KWaylandServer::OutputChangeSetV2 *changeset = it.value(); - - AbstractOutput* output = findOutput(it.key()->uuid()); + auto output = qobject_cast(findOutput(it.key()->uuid())); if (!output) { qCWarning(KWIN_CORE) << "Could NOT find output matching " << it.key()->uuid(); continue; } + auto props = cfg.changeSet(output); + props->enabled = changeset->enabled(); + props->pos = changeset->position(); + props->scale = changeset->scale(); + props->modeSize = changeset->size(); + props->refreshRate = changeset->refreshRate(); + props->transform = static_cast(changeset->transform()); + props->overscan = changeset->overscan(); + props->rgbRange = static_cast(changeset->rgbRange()); + props->vrrPolicy = static_cast(changeset->vrrPolicy()); + } - qDebug(KWIN_CORE) << "Platform::requestOutputsChange enabling" << changeset << it.key()->uuid() << changeset->enabledChanged() << changeset->enabled(); - - if (changeset->enabledChanged() && - changeset->enabled()) { - output->setEnabled(true); + const auto outputs = enabledOutputs(); + bool allDisabled = !std::any_of(outputs.begin(), outputs.end(), [&cfg](const auto &output){ + auto o = qobject_cast(output); + if (!o) { + qCWarning(KWIN_CORE) << "Platform::requestOutputsChange should only be called for Wayland platforms!"; + return false; } - - output->applyChanges(changeset); + return cfg.changeSet(o)->enabled; + }); + if (allDisabled) { + qCWarning(KWIN_CORE) << "Disabling all outputs through configuration changes is not allowed"; + config->setFailed(); + return; } - //process any disable requests - for (auto it = changes.begin(); it != changes.end(); it++) { - const KWaylandServer::OutputChangeSetV2 *changeset = it.value(); - - if (changeset->enabledChanged() && !changeset->enabled()) { - if (enabledOutputs().count() == 1) { - // TODO: check beforehand this condition and set failed otherwise - // TODO: instead create a dummy output? - qCWarning(KWIN_CORE) << "Not disabling final screen" << it.key()->uuid(); - continue; - } - auto output = findOutput(it.key()->uuid()); - if (!output) { - qCWarning(KWIN_CORE) << "Could NOT find output matching " << it.key()->uuid(); - continue; - } - qDebug(KWIN_CORE) << "Platform::requestOutputsChange disabling false" << it.key()->uuid(); - output->setEnabled(false); + if (applyOutputChanges(cfg)) { + if (config->primaryChanged()) { + setPrimaryOutput(findOutput(config->primary()->uuid())); } + Q_EMIT screens()->changed(); + config->setApplied(); + } else { + qCDebug(KWIN_CORE) << "Applying config failed"; + config->setFailed(); } +} - if (config->primaryChanged()) { - setPrimaryOutput(findOutput(config->primary()->uuid())); +bool Platform::applyOutputChanges(const WaylandOutputConfig &config) +{ + const auto outputs = enabledOutputs(); + for (const auto &output : outputs) { + static_cast(output)->applyChanges(config); } - - Q_EMIT screens()->changed(); - config->setApplied(); + return true; } AbstractOutput *Platform::findOutput(int screenId) const diff --git a/src/platform.h b/src/platform.h index e2f26984d0..e5c12712c4 100644 --- a/src/platform.h +++ b/src/platform.h @@ -42,6 +42,7 @@ class Scene; class ScreenEdges; class Session; class Toplevel; +class WaylandOutputConfig; class KWIN_EXPORT Outputs : public QVector { @@ -521,6 +522,11 @@ protected: virtual void doShowCursor(); virtual void doSetSoftwareCursor(); + /** + * Applies the output changes. Default implementation only sets values common between platforms + */ + virtual bool applyOutputChanges(const WaylandOutputConfig &config); + private: void triggerCursorRepaint(); bool m_softwareCursor = false; diff --git a/src/waylandoutputconfig.cpp b/src/waylandoutputconfig.cpp new file mode 100644 index 0000000000..d36a820619 --- /dev/null +++ b/src/waylandoutputconfig.cpp @@ -0,0 +1,39 @@ +/* + 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 "waylandoutputconfig.h" + +namespace KWin +{ + +QSharedPointer WaylandOutputConfig::changeSet(AbstractWaylandOutput *output) +{ + const auto ptr = constChangeSet(output); + m_properties[output] = ptr; + return ptr; +} + +QSharedPointer WaylandOutputConfig::constChangeSet(AbstractWaylandOutput *output) const +{ + if (!m_properties.contains(output)) { + auto props = QSharedPointer::create(); + props->enabled = output->isEnabled(); + props->pos = output->geometry().topLeft(); + props->scale = output->scale(); + props->modeSize = output->modeSize(); + props->refreshRate = output->refreshRate(); + props->transform = output->transform(); + props->overscan = output->overscan(); + props->rgbRange = output->rgbRange(); + props->vrrPolicy = output->vrrPolicy(); + return props; + } + return m_properties[output]; +} + +} diff --git a/src/waylandoutputconfig.h b/src/waylandoutputconfig.h new file mode 100644 index 0000000000..0b0d24307c --- /dev/null +++ b/src/waylandoutputconfig.h @@ -0,0 +1,44 @@ +/* + 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 "abstract_wayland_output.h" +#include "kwin_export.h" + +namespace KWin +{ + +class KWIN_EXPORT OutputChangeSet +{ +public: + bool enabled; + QPoint pos; + float scale; + QSize modeSize; + uint32_t refreshRate; + AbstractWaylandOutput::Transform transform; + uint32_t overscan; + AbstractWaylandOutput::RgbRange rgbRange; + RenderLoop::VrrPolicy vrrPolicy; +}; + +class KWIN_EXPORT WaylandOutputConfig +{ +public: + QSharedPointer changeSet(AbstractWaylandOutput *output); + QSharedPointer constChangeSet(AbstractWaylandOutput *output) const; + +private: + QMap> m_properties; +}; + +}