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; +}; + +}