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.
This commit is contained in:
Xaver Hugl 2021-09-28 10:29:56 +02:00
parent 29e5864402
commit e2a0863843
25 changed files with 686 additions and 735 deletions

View file

@ -135,6 +135,7 @@ set(kwin_SRCS
wayland_server.cpp wayland_server.cpp
waylandclient.cpp waylandclient.cpp
waylandoutput.cpp waylandoutput.cpp
waylandoutputconfig.cpp
waylandoutputdevicev2.cpp waylandoutputdevicev2.cpp
waylandshellintegration.cpp waylandshellintegration.cpp
window_property_notify_x11_filter.cpp window_property_notify_x11_filter.cpp

View file

@ -100,11 +100,6 @@ void AbstractOutput::setEnabled(bool enable)
Q_UNUSED(enable) Q_UNUSED(enable)
} }
void AbstractOutput::applyChanges(const KWaylandServer::OutputChangeSetV2 *changeSet)
{
Q_UNUSED(changeSet)
}
bool AbstractOutput::isInternal() const bool AbstractOutput::isInternal() const
{ {
return false; return false;

View file

@ -117,13 +117,6 @@ public:
*/ */
virtual void setEnabled(bool enable); 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. * Returns geometry of this output in device independent pixels.
*/ */

View file

@ -9,6 +9,7 @@
*/ */
#include "abstract_wayland_output.h" #include "abstract_wayland_output.h"
#include "screens.h" #include "screens.h"
#include "waylandoutputconfig.h"
// KWayland // KWayland
#include <KWaylandServer/outputchangeset_v2.h> #include <KWaylandServer/outputchangeset_v2.h>
@ -20,11 +21,6 @@
namespace KWin namespace KWin
{ {
static AbstractWaylandOutput::Transform outputDeviceTransformToKWinTransform(KWaylandServer::OutputDeviceV2Interface::Transform transform)
{
return static_cast<AbstractWaylandOutput::Transform>(transform);
}
AbstractWaylandOutput::AbstractWaylandOutput(QObject *parent) AbstractWaylandOutput::AbstractWaylandOutput(QObject *parent)
: AbstractOutput(parent) : AbstractOutput(parent)
{ {
@ -152,59 +148,18 @@ void AbstractWaylandOutput::setSubPixelInternal(SubPixel subPixel)
m_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(); Q_EMIT aboutToChange();
qCDebug(KWIN_CORE) << "Apply changes to the Wayland output."; setEnabled(props->enabled);
bool emitModeChanged = false; updateTransform(props->transform);
bool overallSizeCheckNeeded = false; 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<RenderLoop::VrrPolicy>(changeSet->vrrPolicy()));
}
if (changeSet->rgbRangeChanged()) {
qDebug(KWIN_CORE) << "Setting rgb range:" << changeSet->rgbRange();
setRgbRange(static_cast<AbstractWaylandOutput::RgbRange>(changeSet->rgbRange()));
}
Q_EMIT changed(); Q_EMIT changed();
overallSizeCheckNeeded |= emitModeChanged;
if (overallSizeCheckNeeded) {
Q_EMIT screens()->changed();
}
if (emitModeChanged) {
Q_EMIT currentModeChanged();
}
} }
bool AbstractWaylandOutput::isEnabled() const bool AbstractWaylandOutput::isEnabled() const
@ -386,11 +341,6 @@ uint32_t AbstractWaylandOutput::overscan() const
return m_overscan; return m_overscan;
} }
void AbstractWaylandOutput::setOverscan(uint32_t overscan)
{
Q_UNUSED(overscan);
}
void AbstractWaylandOutput::setVrrPolicy(RenderLoop::VrrPolicy policy) void AbstractWaylandOutput::setVrrPolicy(RenderLoop::VrrPolicy policy)
{ {
if (renderLoop()->vrrPolicy() != policy && (m_capabilities & Capability::Vrr)) { if (renderLoop()->vrrPolicy() != policy && (m_capabilities & Capability::Vrr)) {
@ -419,11 +369,6 @@ AbstractWaylandOutput::RgbRange AbstractWaylandOutput::rgbRange() const
return m_rgbRange; return m_rgbRange;
} }
void AbstractWaylandOutput::setRgbRange(RgbRange range)
{
Q_UNUSED(range)
}
void AbstractWaylandOutput::setRgbRangeInternal(RgbRange range) void AbstractWaylandOutput::setRgbRangeInternal(RgbRange range)
{ {
if (m_rgbRange != range) { if (m_rgbRange != range) {

View file

@ -18,14 +18,11 @@
#include <QTimer> #include <QTimer>
#include <QSize> #include <QSize>
namespace KWaylandServer
{
class OutputChangeSet;
}
namespace KWin namespace KWin
{ {
class WaylandOutputConfig;
/** /**
* Generic output representation in a Wayland session * Generic output representation in a Wayland session
*/ */
@ -121,7 +118,7 @@ public:
void moveTo(const QPoint &pos); void moveTo(const QPoint &pos);
void setScale(qreal scale); void setScale(qreal scale);
void applyChanges(const KWaylandServer::OutputChangeSetV2 *changeSet) override; void applyChanges(const WaylandOutputConfig &config);
bool isEnabled() const override; bool isEnabled() const override;
void setEnabled(bool enable) override; void setEnabled(bool enable) override;
@ -136,7 +133,6 @@ public:
virtual void setDpmsMode(DpmsMode mode); virtual void setDpmsMode(DpmsMode mode);
uint32_t overscan() const; uint32_t overscan() const;
virtual void setOverscan(uint32_t overscan);
/** /**
* Returns a matrix that can translate into the display's coordinates system * Returns a matrix that can translate into the display's coordinates system
@ -150,8 +146,6 @@ public:
void setVrrPolicy(RenderLoop::VrrPolicy policy); void setVrrPolicy(RenderLoop::VrrPolicy policy);
RenderLoop::VrrPolicy vrrPolicy() const; RenderLoop::VrrPolicy vrrPolicy() const;
virtual void setRgbRange(RgbRange range);
RgbRange rgbRange() const; RgbRange rgbRange() const;
bool isPlaceholder() const; bool isPlaceholder() const;
@ -183,14 +177,6 @@ protected:
virtual void updateEnablement(bool enable) { virtual void updateEnablement(bool enable) {
Q_UNUSED(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) { virtual void updateTransform(Transform transform) {
Q_UNUSED(transform); Q_UNUSED(transform);
} }

View file

@ -25,6 +25,7 @@
#include "egl_multi_backend.h" #include "egl_multi_backend.h"
#include "drm_pipeline.h" #include "drm_pipeline.h"
#include "drm_virtual_output.h" #include "drm_virtual_output.h"
#include "waylandoutputconfig.h"
#if HAVE_GBM #if HAVE_GBM
#include "egl_gbm_backend.h" #include "egl_gbm_backend.h"
#include <gbm.h> #include <gbm.h>
@ -351,7 +352,7 @@ void DrmBackend::updateOutputs()
} }
}); });
if (oldOutputs != m_outputs) { if (oldOutputs != m_outputs) {
readOutputsConfiguration(); readOutputsConfiguration(m_outputs);
} }
Q_EMIT screensQueried(); Q_EMIT screensQueried();
} }
@ -443,40 +444,43 @@ namespace KWinKScreenIntegration
} }
} }
void DrmBackend::readOutputsConfiguration() void DrmBackend::readOutputsConfiguration(const QVector<DrmAbstractOutput*> &outputs)
{ {
if (m_outputs.isEmpty()) { const auto outputsInfo = KWinKScreenIntegration::outputsConfig(outputs);
return;
}
const auto outputsInfo = KWinKScreenIntegration::outputsConfig(m_outputs);
AbstractOutput *primaryOutput = m_outputs.constFirst(); AbstractOutput *primaryOutput = m_outputs.constFirst();
WaylandOutputConfig cfg;
// default position goes from left to right // default position goes from left to right
QPoint pos(0, 0); QPoint pos(0, 0);
for (auto it = m_outputs.begin(); it != m_outputs.end(); ++it) { for (const auto &output : qAsConst(outputs)) {
const QJsonObject outputInfo = outputsInfo[*it]; auto props = cfg.changeSet(output);
qCDebug(KWIN_DRM) << "Reading output configuration for " << *it; const QJsonObject outputInfo = outputsInfo[output];
qCDebug(KWIN_DRM) << "Reading output configuration for " << output;
if (!outputInfo.isEmpty()) { if (!outputInfo.isEmpty()) {
if (outputInfo["primary"].toBool()) { if (outputInfo["primary"].toBool()) {
primaryOutput = *it; primaryOutput = output;
} }
props->enabled = outputInfo["enabled"].toBool(true);
const QJsonObject pos = outputInfo["pos"].toObject(); 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()) { 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()) { if (const QJsonObject mode = outputInfo["mode"].toObject(); !mode.isEmpty()) {
const QJsonObject size = mode["size"].toObject(); 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 { } else {
(*it)->moveTo(pos); props->enabled = true;
(*it)->updateTransform(DrmOutput::Transform::Normal); 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); setPrimaryOutput(primaryOutput);
} }
@ -497,12 +501,9 @@ void DrmBackend::enableOutput(DrmAbstractOutput *output, bool enable)
} }
} else { } else {
if (m_enabledOutputs.count() == 1) { if (m_enabledOutputs.count() == 1) {
for (const auto &o : qAsConst(m_outputs)) { auto outputs = m_outputs;
if (o != output) { outputs.removeOne(output);
o->setEnabled(true); readOutputsConfiguration(outputs);
break;
}
}
} }
if (m_enabledOutputs.count() == 1 && !kwinApp()->isTerminating()) { if (m_enabledOutputs.count() == 1 && !kwinApp()->isTerminating()) {
qCDebug(KWIN_DRM) << "adding placeholder output"; 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) AbstractOutput *DrmBackend::createVirtualOutput(const QString &name, const QSize &size, double scale)
{ {
auto output = primaryGpu()->createVirtualOutput(name, size * scale, scale, DrmGpu::Full); auto output = primaryGpu()->createVirtualOutput(name, size * scale, scale, DrmGpu::Full);
readOutputsConfiguration(); readOutputsConfiguration(m_outputs);
Q_EMIT screensQueried(); Q_EMIT screensQueried();
return output; return output;
} }
@ -733,4 +734,36 @@ DrmGpu *DrmBackend::findGpuByFd(int fd) const
return nullptr; return nullptr;
} }
bool DrmBackend::applyOutputChanges(const WaylandOutputConfig &config)
{
QVector<DrmOutput*> changed;
for (const auto &gpu : qAsConst(m_gpus)) {
const auto &outputs = gpu->outputs();
for (const auto &o : outputs) {
DrmOutput *output = qobject_cast<DrmOutput*>(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<DrmOutput*>(output)) {
drmOutput->applyQueuedChanges(config);
} else {
output->applyChanges(config);
}
};
updateCursor();
return true;
}
} }

View file

@ -82,6 +82,7 @@ protected:
void doHideCursor() override; void doHideCursor() override;
void doShowCursor() override; void doShowCursor() override;
void doSetSoftwareCursor() override; void doSetSoftwareCursor() override;
bool applyOutputChanges(const WaylandOutputConfig &config) override;
private: private:
friend class DrmGpu; friend class DrmGpu;
@ -94,7 +95,7 @@ private:
void updateCursor(); void updateCursor();
void moveCursor(); void moveCursor();
void initCursor(); void initCursor();
void readOutputsConfiguration(); void readOutputsConfiguration(const QVector<DrmAbstractOutput*> &outputs);
void handleUdevEvent(); void handleUdevEvent();
DrmGpu *addGpu(const QString &fileName); DrmGpu *addGpu(const QString &fileName);

View file

@ -183,18 +183,14 @@ void DrmGpu::initDrmResources()
} }
if (m_planes.isEmpty()) { if (m_planes.isEmpty()) {
qCWarning(KWIN_DRM) << "Failed to create any plane. Falling back to legacy mode on GPU " << m_devNode; 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 { } else {
qCWarning(KWIN_DRM) << "Failed to get plane resources. Falling back to legacy mode on GPU " << m_devNode; qCWarning(KWIN_DRM) << "Failed to get plane resources. Falling back to legacy mode on GPU " << m_devNode;
m_atomicModeSetting = false;
} }
} else { } else {
qCWarning(KWIN_DRM) << "drmSetClientCap for Atomic Mode Setting failed. Using legacy mode on GPU" << m_devNode; 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<drmModeRes> resources(drmModeGetResources(m_fd)); DrmScopedPointer<drmModeRes> resources(drmModeGetResources(m_fd));
if (!resources) { if (!resources) {
@ -266,6 +262,7 @@ bool DrmGpu::updateOutputs()
continue; continue;
} }
m_connectors << c; m_connectors << c;
m_pipelines << c->pipeline();
} else { } else {
(*it)->updateProperties(); (*it)->updateProperties();
if ((*it)->isConnected()) { if ((*it)->isConnected()) {
@ -280,17 +277,30 @@ bool DrmGpu::updateOutputs()
removeLeaseOutput(leaseOutput); removeLeaseOutput(leaseOutput);
} }
m_connectors.removeOne(connector); m_connectors.removeOne(connector);
m_pipelines.removeOne(connector->pipeline());
delete connector; delete connector;
} }
// find unused and connected connectors // find unused and connected connectors
QVector<DrmConnector *> connectedConnectors;
for (const auto &conn : qAsConst(m_connectors)) { for (const auto &conn : qAsConst(m_connectors)) {
auto output = findOutput(conn->id()); auto output = findOutput(conn->id());
if (conn->isConnected()) { if (conn->isConnected()) {
connectedConnectors << conn;
if (output) { if (output) {
output->updateModes(); 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) { } else if (output) {
removeOutput(output); removeOutput(output);
@ -302,170 +312,100 @@ bool DrmGpu::updateOutputs()
// update crtc properties // update crtc properties
for (const auto &crtc : qAsConst(m_crtcs)) { for (const auto &crtc : qAsConst(m_crtcs)) {
crtc->updateProperties(); crtc->updateProperties();
crtc->setLegacyCursor();
} }
// update plane properties // update plane properties
for (const auto &plane : qAsConst(m_planes)) { for (const auto &plane : qAsConst(m_planes)) {
plane->updateProperties(); plane->updateProperties();
} }
// stash away current pipelines of active outputs if (testPendingConfiguration()) {
QMap<DrmOutput*, DrmPipeline*> oldPipelines; for (const auto &pipeline : qAsConst(m_pipelines)) {
for (const auto &output : qAsConst(m_drmOutputs)) { pipeline->applyPendingChanges();
if (!output->isEnabled()) { if (!pipeline->pending.crtc && pipeline->output()) {
// create render resources for findWorkingCombination pipeline->output()->setEnabled(false);
Q_EMIT outputEnabled(output); }
}
} 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<DrmConnector*> connectors, QVector<DrmCrtc*> 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) { if (m_atomicModeSetting) {
// sort outputs by being already connected (to any CRTC) so that already working outputs get preferred // try the crtc that this connector is already connected to first
std::sort(connectedConnectors.begin(), connectedConnectors.end(), [](auto c1, auto c2){ std::sort(crtcs.begin(), crtcs.end(), [connector](auto c1, auto c2){
return c1->getProp(DrmConnector::PropertyIndex::CrtcId)->current() > c2->getProp(DrmConnector::PropertyIndex::CrtcId)->current(); 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<drmModeEncoder> 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<DrmConnector *> connectors;
for (const auto &conn : qAsConst(m_connectors)) {
if (conn->isConnected()) {
connectors << conn;
}
}
auto crtcs = m_crtcs; auto crtcs = m_crtcs;
// don't touch resources that are leased // don't touch resources that are leased
for (const auto &output : qAsConst(m_leaseOutputs)) { for (const auto &output : qAsConst(m_leaseOutputs)) {
if (output->lease()) { if (output->lease()) {
connectors.removeOne(output->pipeline()->connector()); connectors.removeOne(output->pipeline()->connector());
crtcs.removeOne(output->pipeline()->crtc()); crtcs.removeOne(output->pipeline()->pending.crtc);
} else {
m_pipelines.removeOne(output->pipeline());
} }
} }
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<DrmPipeline *> DrmGpu::findWorkingCombination(const QVector<DrmPipeline *> &pipelines, QVector<DrmConnector *> connectors, QVector<DrmCrtc *> 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) { if (m_atomicModeSetting) {
// try the crtc that this connector is already connected to first // sort outputs by being already connected (to any CRTC) so that already working outputs get preferred
std::sort(crtcs.begin(), crtcs.end(), [connector](auto c1, auto c2){ std::sort(connectors.begin(), connectors.end(), [](auto c1, auto c2){
Q_UNUSED(c2) return c1->getProp(DrmConnector::PropertyIndex::CrtcId)->current() > c2->getProp(DrmConnector::PropertyIndex::CrtcId)->current();
if (connector->getProp(DrmConnector::PropertyIndex::CrtcId)->current() == c1->id()) {
return true;
} else {
return false;
}
}); });
} }
return checkCrtcAssignment(connectors, crtcs);
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<drmModeEncoder> 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<DrmPipeline *> &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;
}
} }
DrmOutput *DrmGpu::findOutput(quint32 connector) DrmOutput *DrmGpu::findOutput(quint32 connector)
@ -585,10 +525,7 @@ void DrmGpu::removeOutput(DrmOutput *output)
m_drmOutputs.removeOne(output); m_drmOutputs.removeOne(output);
m_outputs.removeOne(output); m_outputs.removeOne(output);
Q_EMIT outputRemoved(output); Q_EMIT outputRemoved(output);
auto pipeline = output->m_pipeline;
delete output; delete output;
m_pipelines.removeOne(pipeline);
delete pipeline;
} }
AbstractEglDrmBackend *DrmGpu::eglBackend() const AbstractEglDrmBackend *DrmGpu::eglBackend() const
@ -662,7 +599,10 @@ void DrmGpu::handleLeaseRequest(KWaylandServer::DrmLeaseV1Interface *leaseReques
for (const auto &connector : conns) { for (const auto &connector : conns) {
auto output = qobject_cast<DrmLeaseOutput*>(connector); auto output = qobject_cast<DrmLeaseOutput*>(connector);
if (m_leaseOutputs.contains(output) && !output->lease()) { if (m_leaseOutputs.contains(output) && !output->lease()) {
output->addLeaseObjects(objects); if (!output->addLeaseObjects(objects)) {
leaseRequest->deny();
return;
}
outputs << output; outputs << output;
} }
} }
@ -704,10 +644,7 @@ void DrmGpu::removeLeaseOutput(DrmLeaseOutput *output)
{ {
qCDebug(KWIN_DRM) << "Removing leased output" << output; qCDebug(KWIN_DRM) << "Removing leased output" << output;
m_leaseOutputs.removeOne(output); m_leaseOutputs.removeOne(output);
auto pipeline = output->pipeline();
delete output; delete output;
m_pipelines.removeOne(pipeline);
delete pipeline;
} }
QVector<DrmAbstractOutput*> DrmGpu::outputs() const QVector<DrmAbstractOutput*> DrmGpu::outputs() const

View file

@ -69,6 +69,7 @@ public:
QVector<DrmAbstractOutput*> outputs() const; QVector<DrmAbstractOutput*> outputs() const;
const QVector<DrmPipeline*> pipelines() const; const QVector<DrmPipeline*> pipelines() const;
bool testPendingConfiguration();
void setGbmDevice(gbm_device *d); void setGbmDevice(gbm_device *d);
void setEglDisplay(EGLDisplay display); void setEglDisplay(EGLDisplay display);
@ -96,8 +97,7 @@ private:
void removeLeaseOutput(DrmLeaseOutput *output); void removeLeaseOutput(DrmLeaseOutput *output);
void initDrmResources(); void initDrmResources();
QVector<DrmPipeline *> findWorkingCombination(const QVector<DrmPipeline *> &pipelines, QVector<DrmConnector *> connectors, QVector<DrmCrtc *> crtcs); bool checkCrtcAssignment(QVector<DrmConnector*> connectors, QVector<DrmCrtc*> crtcs);
bool commitCombination(const QVector<DrmPipeline *> &pipelines);
void handleLeaseRequest(KWaylandServer::DrmLeaseV1Interface *leaseRequest); void handleLeaseRequest(KWaylandServer::DrmLeaseV1Interface *leaseRequest);
void handleLeaseRevoked(KWaylandServer::DrmLeaseV1Interface *lease); void handleLeaseRevoked(KWaylandServer::DrmLeaseV1Interface *lease);

View file

@ -39,14 +39,19 @@ DrmLeaseOutput::~DrmLeaseOutput()
qCDebug(KWIN_DRM) << "revoking lease offer for connector" << m_pipeline->connector()->id(); qCDebug(KWIN_DRM) << "revoking lease offer for connector" << m_pipeline->connector()->id();
} }
void DrmLeaseOutput::addLeaseObjects(QVector<uint32_t> &objectList) bool DrmLeaseOutput::addLeaseObjects(QVector<uint32_t> &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"; qCDebug(KWIN_DRM) << "adding connector" << m_pipeline->connector()->id() << "to lease";
objectList << m_pipeline->connector()->id(); objectList << m_pipeline->connector()->id();
objectList << m_pipeline->crtc()->id(); objectList << m_pipeline->pending.crtc->id();
if (m_pipeline->primaryPlane()) { if (m_pipeline->pending.crtc->primaryPlane()) {
objectList << m_pipeline->primaryPlane()->id(); objectList << m_pipeline->pending.crtc->primaryPlane()->id();
} }
return true;
} }
void DrmLeaseOutput::leased(KWaylandServer::DrmLeaseV1Interface *lease) void DrmLeaseOutput::leased(KWaylandServer::DrmLeaseV1Interface *lease)
@ -60,11 +65,4 @@ void DrmLeaseOutput::leaseEnded()
m_lease = nullptr; 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;
}
} }

View file

@ -31,7 +31,7 @@ public:
DrmLeaseOutput(DrmPipeline *pipeline, KWaylandServer::DrmLeaseDeviceV1Interface *leaseDevice); DrmLeaseOutput(DrmPipeline *pipeline, KWaylandServer::DrmLeaseDeviceV1Interface *leaseDevice);
~DrmLeaseOutput() override; ~DrmLeaseOutput() override;
void addLeaseObjects(QVector<uint32_t> &objectList); bool addLeaseObjects(QVector<uint32_t> &objectList);
void leased(KWaylandServer::DrmLeaseV1Interface *lease); void leased(KWaylandServer::DrmLeaseV1Interface *lease);
void leaseEnded(); void leaseEnded();
@ -42,7 +42,6 @@ public:
DrmPipeline *pipeline() const { DrmPipeline *pipeline() const {
return m_pipeline; return m_pipeline;
} }
void setPipeline(DrmPipeline *pipeline);
private: private:
DrmPipeline *m_pipeline; DrmPipeline *m_pipeline;

View file

@ -11,6 +11,7 @@
#include "drm_gpu.h" #include "drm_gpu.h"
#include "drm_pointer.h" #include "drm_pointer.h"
#include "logging.h" #include "logging.h"
#include "drm_pipeline.h"
#include <main.h> #include <main.h>
// frameworks // frameworks
@ -42,6 +43,7 @@ DrmConnector::DrmConnector(DrmGpu *gpu, uint32_t connectorId)
QByteArrayLiteral("Limited 16:235") QByteArrayLiteral("Limited 16:235")
}), }),
}, DRM_MODE_OBJECT_CONNECTOR) }, DRM_MODE_OBJECT_CONNECTOR)
, m_pipeline(new DrmPipeline(this))
, m_conn(drmModeGetConnector(gpu->fd(), connectorId)) , m_conn(drmModeGetConnector(gpu->fd(), connectorId))
{ {
if (m_conn) { if (m_conn) {
@ -363,6 +365,11 @@ const Edid *DrmConnector::edid() const
return &m_edid; return &m_edid;
} }
DrmPipeline *DrmConnector::pipeline() const
{
return m_pipeline.data();
}
QDebug& operator<<(QDebug& s, const KWin::DrmConnector *obj) QDebug& operator<<(QDebug& s, const KWin::DrmConnector *obj)
{ {
QDebugStateSaver saver(s); QDebugStateSaver saver(s);

View file

@ -22,6 +22,8 @@
namespace KWin namespace KWin
{ {
class DrmPipeline;
class DrmConnector : public DrmObject class DrmConnector : public DrmObject
{ {
public: public:
@ -56,6 +58,7 @@ public:
bool isConnected() const; bool isConnected() const;
bool isNonDesktop() const; bool isNonDesktop() const;
bool isInternal() const; bool isInternal() const;
DrmPipeline *pipeline() const;
const Edid *edid() const; const Edid *edid() const;
QString connectorName() const; QString connectorName() const;
@ -83,6 +86,7 @@ public:
AbstractWaylandOutput::RgbRange rgbRange() const; AbstractWaylandOutput::RgbRange rgbRange() const;
private: private:
QScopedPointer<DrmPipeline> m_pipeline;
DrmScopedPointer<drmModeConnector> m_conn; DrmScopedPointer<drmModeConnector> m_conn;
QVector<uint32_t> m_encoders; QVector<uint32_t> m_encoders;
Edid m_edid; Edid m_edid;

View file

@ -21,6 +21,7 @@
#include "renderloop_p.h" #include "renderloop_p.h"
#include "screens.h" #include "screens.h"
#include "session.h" #include "session.h"
#include "waylandoutputconfig.h"
// Qt // Qt
#include <QMatrix4x4> #include <QMatrix4x4>
#include <QCryptographicHash> #include <QCryptographicHash>
@ -34,8 +35,8 @@
namespace KWin namespace KWin
{ {
DrmOutput::DrmOutput(DrmGpu *gpu, DrmPipeline *pipeline) DrmOutput::DrmOutput(DrmPipeline *pipeline)
: DrmAbstractOutput(gpu) : DrmAbstractOutput(pipeline->connector()->gpu())
, m_pipeline(pipeline) , m_pipeline(pipeline)
, m_connector(pipeline->connector()) , m_connector(pipeline->connector())
{ {
@ -68,7 +69,6 @@ DrmOutput::DrmOutput(DrmGpu *gpu, DrmPipeline *pipeline)
DrmOutput::~DrmOutput() DrmOutput::~DrmOutput()
{ {
hideCursor();
if (m_pageFlipPending) { if (m_pageFlipPending) {
pageFlipped(); pageFlipped();
} }
@ -78,14 +78,14 @@ DrmOutput::~DrmOutput()
bool DrmOutput::initCursor(const QSize &cursorSize) bool DrmOutput::initCursor(const QSize &cursorSize)
{ {
m_cursor = QSharedPointer<DrmDumbBuffer>::create(m_gpu, cursorSize); m_cursor = QSharedPointer<DrmDumbBuffer>::create(m_gpu, cursorSize);
if (!m_cursor->map(QImage::Format_ARGB32_Premultiplied)) { return m_cursor->map(QImage::Format_ARGB32_Premultiplied);
return false;
}
return updateCursor();
} }
bool DrmOutput::hideCursor() bool DrmOutput::hideCursor()
{ {
if (!isEnabled()) {
return true;
}
bool visibleBefore = m_pipeline->isCursorVisible(); bool visibleBefore = m_pipeline->isCursorVisible();
if (m_pipeline->setCursor(nullptr)) { if (m_pipeline->setCursor(nullptr)) {
if (RenderLoopPrivate::get(m_renderLoop)->presentMode == RenderLoopPrivate::SyncMode::Adaptive if (RenderLoopPrivate::get(m_renderLoop)->presentMode == RenderLoopPrivate::SyncMode::Adaptive
@ -100,6 +100,9 @@ bool DrmOutput::hideCursor()
bool DrmOutput::showCursor() bool DrmOutput::showCursor()
{ {
if (!isEnabled()) {
return true;
}
bool visibleBefore = m_pipeline->isCursorVisible(); bool visibleBefore = m_pipeline->isCursorVisible();
const Cursor * const cursor = Cursors::self()->currentCursor(); const Cursor * const cursor = Cursors::self()->currentCursor();
if (m_pipeline->setCursor(m_cursor, logicalToNativeMatrix(cursor->rect(), scale(), transform()).map(cursor->hotspot()) )) { 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() bool DrmOutput::updateCursor()
{ {
if (!isEnabled()) {
return true;
}
const Cursor *cursor = Cursors::self()->currentCursor(); const Cursor *cursor = Cursors::self()->currentCursor();
if (!cursor) { if (!cursor) {
hideCursor(); hideCursor();
@ -161,6 +167,9 @@ bool DrmOutput::updateCursor()
bool DrmOutput::moveCursor() bool DrmOutput::moveCursor()
{ {
if (!isEnabled()) {
return true;
}
Cursor *cursor = Cursors::self()->currentCursor(); Cursor *cursor = Cursors::self()->currentCursor();
const QMatrix4x4 hotspotMatrix = logicalToNativeMatrix(cursor->rect(), scale(), transform()); const QMatrix4x4 hotspotMatrix = logicalToNativeMatrix(cursor->rect(), scale(), transform());
const QMatrix4x4 monitorMatrix = logicalToNativeMatrix(geometry(), scale(), transform()); const QMatrix4x4 monitorMatrix = logicalToNativeMatrix(geometry(), scale(), transform());
@ -222,11 +231,7 @@ void DrmOutput::initOutputDevice()
void DrmOutput::updateEnablement(bool enable) void DrmOutput::updateEnablement(bool enable)
{ {
if (m_pipeline->setActive(enable)) { m_gpu->platform()->enableOutput(this, enable);
m_gpu->platform()->enableOutput(this, enable);
} else {
qCCritical(KWIN_DRM) << "failed to update enablement to" << enable;
}
} }
void DrmOutput::setDpmsMode(DpmsMode mode) void DrmOutput::setDpmsMode(DpmsMode mode)
@ -241,27 +246,26 @@ void DrmOutput::setDpmsMode(DpmsMode mode)
} }
} else { } else {
m_turnOffTimer.stop(); m_turnOffTimer.stop();
setDrmDpmsMode(mode); if (mode != dpmsMode() && setDrmDpmsMode(mode)) {
if (mode != dpmsMode()) {
setDpmsModeInternal(mode);
Q_EMIT wakeUp(); Q_EMIT wakeUp();
} }
} }
} }
void DrmOutput::setDrmDpmsMode(DpmsMode mode) bool DrmOutput::setDrmDpmsMode(DpmsMode mode)
{ {
if (!isEnabled()) { if (!isEnabled()) {
return; return false;
} }
bool active = mode == DpmsMode::On; bool active = mode == DpmsMode::On;
bool isActive = dpmsMode() == DpmsMode::On; bool isActive = dpmsMode() == DpmsMode::On;
if (active == isActive) { if (active == isActive) {
setDpmsModeInternal(mode); 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); setDpmsModeInternal(mode);
if (active) { if (active) {
m_renderLoop->uninhibit(); m_renderLoop->uninhibit();
@ -273,6 +277,14 @@ void DrmOutput::setDrmDpmsMode(DpmsMode mode)
m_renderLoop->inhibit(); m_renderLoop->inhibit();
m_gpu->platform()->createDpmsFilter(); 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) void DrmOutput::updateTransform(Transform transform)
{ {
setTransformInternal(transform); setTransformInternal(transform);
const auto planeTransform = outputToPlaneTransform(transform);
static bool valid; static bool valid;
// If not set or wrong value, assume KWIN_DRM_SW_ROTATIONS_ONLY=1 until DRM transformations are reliable // 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; static int envOnlySoftwareRotations = qEnvironmentVariableIntValue("KWIN_DRM_SW_ROTATIONS_ONLY", &valid) != 0;
if (valid && !envOnlySoftwareRotations && !m_pipeline->setTransformation(planeTransform)) { if (valid && !envOnlySoftwareRotations) {
qCDebug(KWIN_DRM) << "setting transformation to" << planeTransform << "failed!"; m_pipeline->pending.transformation = outputToPlaneTransform(transform);
// just in case, if we had any rotation before, clear it if (!DrmPipeline::commitPipelines({m_pipeline}, DrmPipeline::CommitMode::Test)) {
m_pipeline->setTransformation(DrmPlane::Transformation::Rotate0); // 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)) {
// show cursor only if is enabled, i.e if pointer device is present m_pipeline->applyPendingChanges();
if (!m_gpu->platform()->isCursorHidden() && !m_gpu->platform()->usesSoftwareCursor()) { } else {
// the cursor might need to get rotated m_pipeline->revertPendingChanges();
showCursor(); qCWarning(KWIN_DRM) << "Can't switch rotation back to Rotate0!";
updateCursor(); }
}
} }
} }
@ -340,40 +352,22 @@ void DrmOutput::updateModes()
// mode changed // mode changed
if (mode.size != modeSize() || mode.refreshRate != refreshRate()) { 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 bool DrmOutput::needsSoftwareTransformation() const
{ {
return m_pipeline->transformation() != outputToPlaneTransform(transform()); return m_pipeline->pending.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);
}
} }
void DrmOutput::pageFlipped() void DrmOutput::pageFlipped()
@ -389,8 +383,14 @@ bool DrmOutput::present(const QSharedPointer<DrmBuffer> &buffer, QRegion damaged
return false; return false;
} }
RenderLoopPrivate *renderLoopPrivate = RenderLoopPrivate::get(m_renderLoop); RenderLoopPrivate *renderLoopPrivate = RenderLoopPrivate::get(m_renderLoop);
if (!m_pipeline->setSyncMode(renderLoopPrivate->presentMode)) { if (m_pipeline->pending.syncMode != renderLoopPrivate->presentMode) {
setVrrPolicy(RenderLoop::VrrPolicy::Never); 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)) { if (m_pipeline->present(buffer)) {
m_pageFlipPending = true; m_pageFlipPending = true;
@ -403,27 +403,22 @@ bool DrmOutput::present(const QSharedPointer<DrmBuffer> &buffer, QRegion damaged
int DrmOutput::gammaRampSize() const 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) bool DrmOutput::setGammaRamp(const GammaRamp &gamma)
{ {
if (m_pipeline->setGammaRamp(gamma)) { m_pipeline->pending.gamma = QSharedPointer<DrmGammaRamp>::create(m_gpu, gamma);
if (DrmPipeline::commitPipelines({m_pipeline}, DrmPipeline::CommitMode::Test)) {
m_pipeline->applyPendingChanges();
m_renderLoop->scheduleRepaint(); m_renderLoop->scheduleRepaint();
return true; return true;
} else { } else {
m_pipeline->revertPendingChanges();
return false; return false;
} }
} }
void DrmOutput::setOverscan(uint32_t overscan)
{
if (m_pipeline->setOverscan(overscan)) {
setOverscanInternal(overscan);
m_renderLoop->scheduleRepaint();
}
}
DrmConnector *DrmOutput::connector() const DrmConnector *DrmOutput::connector() const
{ {
return m_connector; return m_connector;
@ -449,18 +444,54 @@ QVector<uint64_t> DrmOutput::supportedModifiers(uint32_t drmFormat) const
return m_pipeline->supportedModifiers(drmFormat); return m_pipeline->supportedModifiers(drmFormat);
} }
void DrmOutput::setRgbRange(RgbRange range) bool DrmOutput::queueChanges(const WaylandOutputConfig &config)
{ {
if (m_pipeline->setRgbRange(range)) { auto props = config.constChangeSet(this);
setRgbRangeInternal(range); m_pipeline->pending.active = props->enabled;
m_renderLoop->scheduleRepaint(); 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!"); Q_EMIT aboutToChange();
m_pipeline = pipeline; 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();
} }
} }

View file

@ -51,25 +51,26 @@ public:
DrmConnector *connector() const; DrmConnector *connector() const;
DrmPipeline *pipeline() const; DrmPipeline *pipeline() const;
void setPipeline(DrmPipeline *pipeline);
QSize sourceSize() const override; QSize sourceSize() const override;
bool isFormatSupported(uint32_t drmFormat) const override; bool isFormatSupported(uint32_t drmFormat) const override;
QVector<uint64_t> supportedModifiers(uint32_t drmFormat) const override; QVector<uint64_t> supportedModifiers(uint32_t drmFormat) const override;
bool needsSoftwareTransformation() const override; bool needsSoftwareTransformation() const override;
bool queueChanges(const WaylandOutputConfig &config);
void applyQueuedChanges(const WaylandOutputConfig &config);
void revertQueuedChanges();
private: private:
friend class DrmGpu; friend class DrmGpu;
friend class DrmBackend; friend class DrmBackend;
DrmOutput(DrmGpu* gpu, DrmPipeline *pipeline); DrmOutput(DrmPipeline *pipeline);
void initOutputDevice(); void initOutputDevice();
void updateEnablement(bool enable) override; void updateEnablement(bool enable) override;
void setDrmDpmsMode(DpmsMode mode); bool setDrmDpmsMode(DpmsMode mode);
void setDpmsMode(DpmsMode mode) override; void setDpmsMode(DpmsMode mode) override;
void applyMode(int modeIndex) override;
void updateMode(const QSize &size, uint32_t refreshRate) override;
void updateModes(); void updateModes();
QVector<AbstractWaylandOutput::Mode> getModes() const; QVector<AbstractWaylandOutput::Mode> getModes() const;
@ -78,8 +79,6 @@ private:
int gammaRampSize() const override; int gammaRampSize() const override;
bool setGammaRamp(const GammaRamp &gamma) override; bool setGammaRamp(const GammaRamp &gamma) override;
void setOverscan(uint32_t overscan) override;
void setRgbRange(RgbRange range) override;
DrmPipeline *m_pipeline; DrmPipeline *m_pipeline;
DrmConnector *m_connector; DrmConnector *m_connector;

View file

@ -34,16 +34,13 @@
namespace KWin namespace KWin
{ {
DrmPipeline::DrmPipeline(DrmGpu *gpu, DrmConnector *conn, DrmCrtc *crtc) DrmPipeline::DrmPipeline(DrmConnector *conn)
: m_output(nullptr) : m_output(nullptr)
, m_gpu(gpu)
, m_connector(conn) , m_connector(conn)
, m_crtc(crtc)
, m_primaryPlane(crtc->primaryPlane())
{ {
m_allObjects << m_connector << m_crtc; if (!gpu()->atomicModeSetting()) {
if (m_primaryPlane) { m_formats.insert(DRM_FORMAT_XRGB8888, {});
m_allObjects << m_primaryPlane; 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<DrmBuffer> &buffer) bool DrmPipeline::present(const QSharedPointer<DrmBuffer> &buffer)
{ {
Q_ASSERT(pending.crtc);
m_primaryBuffer = buffer; 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, // EglStreamBackend queues normal page flips through EGL,
// modesets etc are performed through DRM-KMS // modesets etc are performed through DRM-KMS
bool needsCommit = std::any_of(m_allObjects.constBegin(), m_allObjects.constEnd(), [](auto obj){return obj->needsCommit();}); if (!m_connector->needsCommit() && !pending.crtc->needsCommit()) {
if (!needsCommit) {
return true; return true;
} }
} }
if (m_gpu->atomicModeSetting()) { if (gpu()->atomicModeSetting()) {
if (!atomicCommit()) { if (!commitPipelines({this}, CommitMode::Commit)) {
// update properties and try again // update properties and try again
updateProperties(); updateProperties();
if (!atomicCommit()) { if (!commitPipelines({this}, CommitMode::Commit)) {
qCWarning(KWIN_DRM) << "Atomic present failed!" << strerror(errno); qCWarning(KWIN_DRM) << "Atomic present failed!" << strerror(errno);
printDebugInfo(); printDebugInfo();
return false; return false;
@ -106,22 +78,16 @@ bool DrmPipeline::present(const QSharedPointer<DrmBuffer> &buffer)
return true; return true;
} }
bool DrmPipeline::atomicCommit()
{
return commitPipelines({this}, CommitMode::Commit);
}
bool DrmPipeline::commitPipelines(const QVector<DrmPipeline*> &pipelines, CommitMode mode) bool DrmPipeline::commitPipelines(const QVector<DrmPipeline*> &pipelines, CommitMode mode)
{ {
Q_ASSERT(!pipelines.isEmpty()); Q_ASSERT(!pipelines.isEmpty());
if (pipelines[0]->gpu()->atomicModeSetting()) {
if (pipelines[0]->m_gpu->atomicModeSetting()) {
drmModeAtomicReq *req = drmModeAtomicAlloc(); drmModeAtomicReq *req = drmModeAtomicAlloc();
if (!req) { if (!req) {
qCDebug(KWIN_DRM) << "Failed to allocate drmModeAtomicReq!" << strerror(errno); qCDebug(KWIN_DRM) << "Failed to allocate drmModeAtomicReq!" << strerror(errno);
return false; return false;
} }
uint32_t flags = 0; uint32_t flags = DRM_MODE_ATOMIC_NONBLOCK;
const auto &failed = [pipelines, req](){ const auto &failed = [pipelines, req](){
drmModeAtomicFree(req); drmModeAtomicFree(req);
for (const auto &pipeline : pipelines) { for (const auto &pipeline : pipelines) {
@ -130,8 +96,10 @@ bool DrmPipeline::commitPipelines(const QVector<DrmPipeline*> &pipelines, Commit
pipeline->m_primaryBuffer = pipeline->m_oldTestBuffer; pipeline->m_primaryBuffer = pipeline->m_oldTestBuffer;
pipeline->m_oldTestBuffer = nullptr; pipeline->m_oldTestBuffer = nullptr;
} }
for (const auto &obj : qAsConst(pipeline->m_allObjects)) { pipeline->m_connector->rollbackPending();
obj->rollbackPending(); if (pipeline->pending.crtc) {
pipeline->pending.crtc->rollbackPending();
pipeline->pending.crtc->primaryPlane()->rollbackPending();
} }
} }
return false; return false;
@ -146,131 +114,103 @@ bool DrmPipeline::commitPipelines(const QVector<DrmPipeline*> &pipelines, Commit
return failed(); 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); qCWarning(KWIN_DRM) << "Atomic test for" << mode << "failed!" << strerror(errno);
return failed(); 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); qCWarning(KWIN_DRM) << "Atomic commit failed! This should never happen!" << strerror(errno);
return failed(); return failed();
} }
for (const auto &pipeline : pipelines) { for (const auto &pipeline : pipelines) {
pipeline->m_oldTestBuffer = nullptr; pipeline->m_oldTestBuffer = nullptr;
for (const auto &obj : qAsConst(pipeline->m_allObjects)) { pipeline->m_connector->commitPending();
obj->commitPending(); if (pipeline->pending.crtc) {
pipeline->pending.crtc->commitPending();
pipeline->pending.crtc->primaryPlane()->commitPending();
} }
if (mode != CommitMode::Test) { if (mode != CommitMode::Test) {
pipeline->m_primaryPlane->setNext(pipeline->m_primaryBuffer); pipeline->m_connector->commit();
for (const auto &obj : qAsConst(pipeline->m_allObjects)) { if (pipeline->pending.crtc) {
obj->commit(); pipeline->pending.crtc->primaryPlane()->setNext(pipeline->m_primaryBuffer);
pipeline->pending.crtc->commit();
pipeline->pending.crtc->primaryPlane()->commit();
} }
pipeline->m_current = pipeline->pending;
} }
} }
drmModeAtomicFree(req); drmModeAtomicFree(req);
return true; return true;
} else { } else {
bool failure = false;
for (const auto &pipeline : pipelines) { for (const auto &pipeline : pipelines) {
if (pipeline->m_legacyNeedsModeset && pipeline->isActive() && !pipeline->modeset(0)) { if (!pipeline->applyPendingChangesLegacy()) {
return false; 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 DrmPipeline::populateAtomicValues(drmModeAtomicReq *req, uint32_t &flags)
{ {
bool usesEglStreams = m_gpu->useEglStreams() && m_gpu->eglBackend() != nullptr && m_gpu == m_gpu->platform()->primaryGpu(); if (needsModeset()) {
if (!usesEglStreams && isActive()) { 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; 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; m_lastFlags = flags;
if (pending.crtc) {
auto modeSize = m_connector->currentMode().size; auto modeSize = m_connector->modes()[pending.modeIndex].size;
m_primaryPlane->set(QPoint(0, 0), m_primaryBuffer ? m_primaryBuffer->size() : modeSize, QPoint(0, 0), modeSize); pending.crtc->primaryPlane()->set(QPoint(0, 0), m_primaryBuffer ? m_primaryBuffer->size() : modeSize, QPoint(0, 0), modeSize);
m_primaryPlane->setBuffer(isActive() ? m_primaryBuffer.get() : nullptr); pending.crtc->primaryPlane()->setBuffer(activePending() ? m_primaryBuffer.get() : nullptr);
for (const auto &obj : qAsConst(m_allObjects)) { pending.crtc->setPending(DrmCrtc::PropertyIndex::VrrEnabled, pending.syncMode == RenderLoopPrivate::SyncMode::Adaptive);
if (!obj->atomicPopulate(req)) { if (pending.gamma != m_current.gamma) {
return false; 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() 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; return false;
} }
m_lastFlags = DRM_MODE_PAGE_FLIP_EVENT; m_lastFlags = DRM_MODE_PAGE_FLIP_EVENT;
m_crtc->setNext(m_primaryBuffer); QVector<DrmPipeline*> *userData = new QVector<DrmPipeline*>();
if (drmModePageFlip(m_gpu->fd(), m_crtc->id(), m_primaryBuffer ? m_primaryBuffer->bufferId() : 0, DRM_MODE_PAGE_FLIP_EVENT, m_output) != 0) { *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; qCWarning(KWIN_DRM) << "Page flip failed:" << strerror(errno) << m_primaryBuffer;
return false; return false;
} }
return true; pending.crtc->setNext(m_primaryBuffer);
}
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);
}
return true; return true;
} }
bool DrmPipeline::checkTestBuffer() bool DrmPipeline::checkTestBuffer()
{ {
if (m_primaryBuffer && m_primaryBuffer->size() == sourceSize()) { if (!pending.crtc || (m_primaryBuffer && m_primaryBuffer->size() == sourceSize())) {
return true; return true;
} }
auto backend = m_gpu->eglBackend(); auto backend = gpu()->eglBackend();
QSharedPointer<DrmBuffer> buffer; QSharedPointer<DrmBuffer> buffer;
// try to re-use buffers if possible. // try to re-use buffers if possible.
const auto &checkBuffer = [this, backend, &buffer](const QSharedPointer<DrmBuffer> &buf){ const auto &checkBuffer = [this, backend, &buffer](const QSharedPointer<DrmBuffer> &buf){
@ -281,29 +221,29 @@ bool DrmPipeline::checkTestBuffer()
buffer = buf; buffer = buf;
} }
}; };
if (m_primaryPlane && m_primaryPlane->next()) { if (pending.crtc->primaryPlane() && pending.crtc->primaryPlane()->next()) {
checkBuffer(m_primaryPlane->next()); checkBuffer(pending.crtc->primaryPlane()->next());
} else if (m_primaryPlane && m_primaryPlane->current()) { } else if (pending.crtc->primaryPlane() && pending.crtc->primaryPlane()->current()) {
checkBuffer(m_primaryPlane->current()); checkBuffer(pending.crtc->primaryPlane()->current());
} else if (m_crtc->next()) { } else if (pending.crtc->next()) {
checkBuffer(m_crtc->next()); checkBuffer(pending.crtc->next());
} else if (m_crtc->current()) { } else if (pending.crtc->current()) {
checkBuffer(m_crtc->current()); checkBuffer(pending.crtc->current());
} }
// if we don't have a fitting buffer already, get or create one // if we don't have a fitting buffer already, get or create one
if (buffer) { if (buffer) {
#if HAVE_GBM #if HAVE_GBM
} else if (backend && m_output) { } else if (backend && m_output) {
buffer = backend->renderTestFrame(m_output); buffer = backend->renderTestFrame(m_output);
} else if (backend && m_gpu->gbmDevice()) { } else if (backend && 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); 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) { if (!bo) {
return false; return false;
} }
buffer = QSharedPointer<DrmGbmBuffer>::create(m_gpu, bo, nullptr); buffer = QSharedPointer<DrmGbmBuffer>::create(gpu(), bo, nullptr);
#endif #endif
} else { } else {
buffer = QSharedPointer<DrmDumbBuffer>::create(m_gpu, sourceSize()); buffer = QSharedPointer<DrmDumbBuffer>::create(gpu(), sourceSize());
} }
if (buffer && buffer->bufferId()) { if (buffer && buffer->bufferId()) {
m_oldTestBuffer = m_primaryBuffer; m_oldTestBuffer = m_primaryBuffer;
@ -315,175 +255,117 @@ bool DrmPipeline::checkTestBuffer()
bool DrmPipeline::setCursor(const QSharedPointer<DrmDumbBuffer> &buffer, const QPoint &hotspot) bool DrmPipeline::setCursor(const QSharedPointer<DrmDumbBuffer> &buffer, const QPoint &hotspot)
{ {
return m_crtc->setLegacyCursor(buffer, hotspot); return pending.crtc->setLegacyCursor(buffer, hotspot);
} }
bool DrmPipeline::moveCursor(QPoint pos) 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 (!pending.crtc) {
if (isActive() && !active) { m_connector->setPending(DrmConnector::PropertyIndex::CrtcId, 0);
if (drmModeSetCursor(m_gpu->fd(), m_crtc->id(), 0, 0, 0) != 0) { return;
qCWarning(KWIN_DRM) << "Could not set cursor:" << strerror(errno);
}
} }
bool success = false; auto mode = m_connector->modes()[pending.modeIndex];
auto mode = m_connector->currentMode().mode;
if (m_gpu->atomicModeSetting()) { m_connector->setPending(DrmConnector::PropertyIndex::CrtcId, activePending() ? pending.crtc->id() : 0);
m_connector->setPending(DrmConnector::PropertyIndex::CrtcId, active ? m_crtc->id() : 0); if (const auto &prop = m_connector->getProp(DrmConnector::PropertyIndex::Broadcast_RGB)) {
m_crtc->setPending(DrmCrtc::PropertyIndex::Active, active); prop->setEnum(pending.rgbRange);
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);
}
} }
if (!success) {
qCWarning(KWIN_DRM) << "Setting active to" << active << "failed" << strerror(errno); pending.crtc->setPending(DrmCrtc::PropertyIndex::Active, activePending());
} pending.crtc->setPendingBlob(DrmCrtc::PropertyIndex::ModeId, activePending() ? &mode.mode : nullptr, sizeof(drmModeModeInfo));
if (isActive()) {
// enable cursor (again) pending.crtc->primaryPlane()->setPending(DrmPlane::PropertyIndex::CrtcId, activePending() ? pending.crtc->id() : 0);
m_crtc->setLegacyCursor(); pending.crtc->primaryPlane()->setTransformation(DrmPlane::Transformation::Rotate0);
} pending.crtc->primaryPlane()->set(QPoint(0, 0), sourceSize(), QPoint(0, 0), mode.size);
return success;
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 if (!pending.crtc) {
// the gamma ramp with AMS -> fall back to legacy without the property pending.active = false;
if (m_gpu->atomicModeSetting() && m_crtc->getProp(DrmCrtc::PropertyIndex::Gamma_LUT)) { }
struct drm_color_lut *gamma = new drm_color_lut[ramp.size()]; m_next = pending;
for (uint32_t i = 0; i < ramp.size(); i++) { }
gamma[i].red = ramp.red()[i];
gamma[i].green = ramp.green()[i]; bool DrmPipeline::applyPendingChangesLegacy()
gamma[i].blue = ramp.blue()[i]; {
} if (!pending.active && pending.crtc) {
bool result = m_crtc->setPendingBlob(DrmCrtc::PropertyIndex::Gamma_LUT, gamma, ramp.size() * sizeof(drm_color_lut)); drmModeSetCursor(gpu()->fd(), pending.crtc->id(), 0, 0, 0);
delete[] gamma; }
if (!result) { if (pending.active) {
qCWarning(KWIN_DRM) << "Could not create gamma LUT property blob" << strerror(errno); 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; 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<uint16_t*>(pending.gamma->lut.red()),
const_cast<uint16_t*>(pending.gamma->lut.blue()),
const_cast<uint16_t*>(pending.gamma->lut.green())) != 0) {
qCWarning(KWIN_DRM) << "Setting gamma failed!" << strerror(errno); qCWarning(KWIN_DRM) << "Setting gamma failed!" << strerror(errno);
return false; return false;
} }
} else {
uint16_t *red = const_cast<uint16_t*>(ramp.red());
uint16_t *green = const_cast<uint16_t*>(ramp.green());
uint16_t *blue = const_cast<uint16_t*>(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) pending.crtc->setLegacyCursor();
{
return setPendingTransformation(transformation) && test();
}
bool DrmPipeline::setPendingTransformation(const DrmPlane::Transformations &transformation)
{
if (this->transformation() == transformation) {
return true;
} }
if (!m_gpu->atomicModeSetting()) { if (!m_connector->getProp(DrmConnector::PropertyIndex::Dpms)->setPropertyLegacy(pending.active ? DRM_MODE_DPMS_ON : DRM_MODE_DPMS_OFF)) {
return false; qCWarning(KWIN_DRM) << "Setting legacy dpms failed!" << strerror(errno);
}
if (!m_primaryPlane->setTransformation(transformation)) {
return false; return false;
} }
return true; return true;
} }
bool DrmPipeline::setSyncMode(RenderLoopPrivate::SyncMode syncMode) bool DrmPipeline::legacyModeset()
{ {
auto vrrProp = m_crtc->getProp(DrmCrtc::PropertyIndex::VrrEnabled); auto mode = m_connector->modes()[pending.modeIndex];
if (!vrrProp || !m_connector->vrrCapable()) { uint32_t connId = m_connector->id();
return syncMode == RenderLoopPrivate::SyncMode::Fixed; 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);
bool vrr = syncMode == RenderLoopPrivate::SyncMode::Adaptive; pending = m_next;
if (vrrProp->pending() == vrr) { m_primaryBuffer = m_oldTestBuffer;
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())) {
return false; return false;
} }
m_connector->setOverscan(overscan, m_connector->currentMode().size); m_oldTestBuffer = nullptr;
return test(); // make sure the buffer gets kept alive, or the modeset gets reverted by the kernel
} if (pending.crtc->current()) {
pending.crtc->setNext(m_primaryBuffer);
bool DrmPipeline::setRgbRange(AbstractWaylandOutput::RgbRange rgbRange)
{
if (const auto &prop = m_connector->getProp(DrmConnector::PropertyIndex::Broadcast_RGB)) {
prop->setEnum(rgbRange);
return test();
} else { } else {
return false; pending.crtc->setCurrent(m_primaryBuffer);
} }
return true;
} }
QSize DrmPipeline::sourceSize() const QSize DrmPipeline::sourceSize() const
{ {
auto mode = m_connector->currentMode(); auto mode = m_connector->modes()[pending.modeIndex];
if (transformation() & (DrmPlane::Transformation::Rotate90 | DrmPlane::Transformation::Rotate270)) { if (pending.transformation & (DrmPlane::Transformation::Rotate90 | DrmPlane::Transformation::Rotate270)) {
return mode.size.transposed(); return mode.size.transposed();
} }
return mode.size; 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 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 QPoint DrmPipeline::cursorPos() const
{ {
return m_crtc->cursorPos(); return pending.crtc->cursorPos();
} }
DrmConnector *DrmPipeline::connector() const DrmConnector *DrmPipeline::connector() const
@ -491,21 +373,16 @@ DrmConnector *DrmPipeline::connector() const
return m_connector; return m_connector;
} }
DrmCrtc *DrmPipeline::crtc() const DrmGpu *DrmPipeline::gpu() const
{ {
return m_crtc; return m_connector->gpu();
}
DrmPlane *DrmPipeline::primaryPlane() const
{
return m_primaryPlane;
} }
void DrmPipeline::pageFlipped() void DrmPipeline::pageFlipped()
{ {
m_crtc->flipBuffer(); m_current.crtc->flipBuffer();
if (m_primaryPlane) { if (m_current.crtc->primaryPlane()) {
m_primaryPlane->flipBuffer(); m_current.crtc->primaryPlane()->flipBuffer();
} }
} }
@ -521,12 +398,16 @@ DrmOutput *DrmPipeline::output() const
void DrmPipeline::updateProperties() void DrmPipeline::updateProperties()
{ {
for (const auto &obj : qAsConst(m_allObjects)) { m_connector->updateProperties();
obj->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 // with legacy we don't know what happened to the cursor after VT switch
// so make sure it gets set again // so make sure it gets set again
m_crtc->setLegacyCursor(); pending.crtc->setLegacyCursor();
} }
bool DrmPipeline::isFormatSupported(uint32_t drmFormat) const bool DrmPipeline::isFormatSupported(uint32_t drmFormat) const
@ -539,6 +420,44 @@ QVector<uint64_t> DrmPipeline::supportedModifiers(uint32_t drmFormat) const
return m_formats[drmFormat]; 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) static void printProps(DrmObject *object)
{ {
auto list = object->properties(); auto list = object->properties();
@ -574,11 +493,13 @@ void DrmPipeline::printDebugInfo() const
qCWarning(KWIN_DRM) << "Drm objects:"; qCWarning(KWIN_DRM) << "Drm objects:";
qCWarning(KWIN_DRM) << "connector" << m_connector->id(); qCWarning(KWIN_DRM) << "connector" << m_connector->id();
printProps(m_connector); printProps(m_connector);
qCWarning(KWIN_DRM) << "crtc" << m_crtc->id(); if (pending.crtc) {
printProps(m_crtc); qCWarning(KWIN_DRM) << "crtc" << pending.crtc->id();
if (m_primaryPlane) { printProps(pending.crtc);
qCWarning(KWIN_DRM) << "primary plane" << m_primaryPlane->id(); if (pending.crtc->primaryPlane()) {
printProps(m_primaryPlane); qCWarning(KWIN_DRM) << "primary plane" << pending.crtc->primaryPlane()->id();
printProps(pending.crtc->primaryPlane());
}
} }
} }

View file

@ -30,40 +30,42 @@ class DrmBuffer;
class DrmDumbBuffer; class DrmDumbBuffer;
class GammaRamp; 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 class DrmPipeline
{ {
public: public:
DrmPipeline(DrmGpu *gpu, DrmConnector *conn, DrmCrtc *crtc); DrmPipeline(DrmConnector *conn);
~DrmPipeline(); ~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 * tests the pending commit first and commits it if the test passes
* if the test fails, there is a guarantee for no lasting changes * if the test fails, there is a guarantee for no lasting changes
*/ */
bool present(const QSharedPointer<DrmBuffer> &buffer); bool present(const QSharedPointer<DrmBuffer> &buffer);
bool modeset(int modeIndex); bool needsModeset() const;
bool setCursor(const QSharedPointer<DrmDumbBuffer> &buffer, const QPoint &hotspot = QPoint()); void prepareModeset();
bool setActive(bool enable); void applyPendingChanges();
bool setGammaRamp(const GammaRamp &ramp); void revertPendingChanges();
bool setTransformation(const DrmPlane::Transformations &transform);
bool moveCursor(QPoint pos); bool setCursor(const QSharedPointer<DrmDumbBuffer> &buffer, const QPoint &hotspot = QPoint());
bool setSyncMode(RenderLoopPrivate::SyncMode syncMode); bool moveCursor(QPoint pos);
bool setOverscan(uint32_t overscan);
bool setRgbRange(AbstractWaylandOutput::RgbRange rgbRange);
DrmPlane::Transformations transformation() const;
bool isCursorVisible() const; bool isCursorVisible() const;
QPoint cursorPos() const; QPoint cursorPos() const;
DrmConnector *connector() const; DrmConnector *connector() const;
DrmCrtc *crtc() const; DrmGpu *gpu() const;
DrmPlane *primaryPlane() const;
void pageFlipped(); void pageFlipped();
void printDebugInfo() const; void printDebugInfo() const;
@ -76,6 +78,18 @@ public:
void setOutput(DrmOutput *output); void setOutput(DrmOutput *output);
DrmOutput *output() const; 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<DrmGammaRamp> gamma;
DrmPlane::Transformations transformation = DrmPlane::Transformation::Rotate0;
};
State pending;
enum class CommitMode { enum class CommitMode {
Test, Test,
Commit Commit
@ -85,29 +99,26 @@ public:
private: private:
bool populateAtomicValues(drmModeAtomicReq *req, uint32_t &flags); bool populateAtomicValues(drmModeAtomicReq *req, uint32_t &flags);
bool test();
bool atomicCommit();
bool presentLegacy(); bool presentLegacy();
bool checkTestBuffer(); bool checkTestBuffer();
bool isActive() const; bool activePending() const;
bool setPendingTransformation(const DrmPlane::Transformations &transformation); bool applyPendingChangesLegacy();
bool legacyModeset();
DrmOutput *m_output = nullptr; DrmOutput *m_output = nullptr;
DrmGpu *m_gpu = nullptr;
DrmConnector *m_connector = nullptr; DrmConnector *m_connector = nullptr;
DrmCrtc *m_crtc = nullptr;
DrmPlane *m_primaryPlane = nullptr;
QSharedPointer<DrmBuffer> m_primaryBuffer; QSharedPointer<DrmBuffer> m_primaryBuffer;
QSharedPointer<DrmBuffer> m_oldTestBuffer; QSharedPointer<DrmBuffer> m_oldTestBuffer;
bool m_legacyNeedsModeset = true;
QVector<DrmObject*> m_allObjects;
QMap<uint32_t, QVector<uint64_t>> m_formats; QMap<uint32_t, QVector<uint64_t>> m_formats;
int m_lastFlags = 0; 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;
}; };
} }

View file

@ -70,6 +70,13 @@ public:
bool needsCommit() const; bool needsCommit() const;
bool setPropertyLegacy(uint64_t value); bool setPropertyLegacy(uint64_t value);
template<typename T>
bool setEnumLegacy(T value) {
if (hasEnum(static_cast<uint64_t>(value))) {
return setPropertyLegacy(m_enumMap[static_cast<uint32_t>(value)]);
}
return false;
}
private: private:
uint32_t m_propId = 0; uint32_t m_propId = 0;

View file

@ -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) void DrmVirtualOutput::setDpmsMode(DpmsMode mode)
{ {
setDpmsModeInternal(mode); setDpmsModeInternal(mode);

View file

@ -47,8 +47,6 @@ public:
private: private:
void vblank(std::chrono::nanoseconds timestamp); 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 setDpmsMode(DpmsMode mode) override;
void updateEnablement(bool enable) override; void updateEnablement(bool enable) override;

View file

@ -307,7 +307,7 @@ bool EglStreamBackend::resetOutput(Output &o)
if (isPrimary()) { if (isPrimary()) {
// dumb buffer used for modesetting // dumb buffer used for modesetting
o.buffer = QSharedPointer<DrmDumbBuffer>::create(m_gpu, sourceSize); o.buffer = QSharedPointer<DrmDumbBuffer>::create(m_gpu, sourceSize);
o.targetPlane = drmOutput->pipeline()->primaryPlane(); o.targetPlane = drmOutput->pipeline()->pending.crtc->primaryPlane();
EGLAttrib streamAttribs[] = { EGLAttrib streamAttribs[] = {
EGL_STREAM_FIFO_LENGTH_KHR, 0, // mailbox mode EGL_STREAM_FIFO_LENGTH_KHR, 0, // mailbox mode
@ -476,7 +476,7 @@ SurfaceTexture *EglStreamBackend::createSurfaceTextureWayland(SurfacePixmapWayla
bool EglStreamBackend::needsReset(const Output &o) const 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; return true;
} }
QSize surfaceSize = o.dumbSwapchain ? o.dumbSwapchain->size() : o.buffer->size(); QSize surfaceSize = o.dumbSwapchain ? o.dumbSwapchain->size() : o.buffer->size();

View file

@ -23,6 +23,7 @@
#include "screenedge.h" #include "screenedge.h"
#include "touch_input.h" #include "touch_input.h"
#include "wayland_server.h" #include "wayland_server.h"
#include "waylandoutputconfig.h"
#include <KWaylandServer/outputconfiguration_v2_interface.h> #include <KWaylandServer/outputconfiguration_v2_interface.h>
#include <KWaylandServer/outputchangeset_v2.h> #include <KWaylandServer/outputchangeset_v2.h>
@ -120,55 +121,61 @@ void Platform::requestOutputsChange(KWaylandServer::OutputConfigurationV2Interfa
return; return;
} }
WaylandOutputConfig cfg;
const auto changes = config->changes(); const auto changes = config->changes();
//process all non-disabling changes
for (auto it = changes.begin(); it != changes.end(); it++) { for (auto it = changes.begin(); it != changes.end(); it++) {
const KWaylandServer::OutputChangeSetV2 *changeset = it.value(); const KWaylandServer::OutputChangeSetV2 *changeset = it.value();
auto output = qobject_cast<AbstractWaylandOutput*>(findOutput(it.key()->uuid()));
AbstractOutput* output = findOutput(it.key()->uuid());
if (!output) { if (!output) {
qCWarning(KWIN_CORE) << "Could NOT find output matching " << it.key()->uuid(); qCWarning(KWIN_CORE) << "Could NOT find output matching " << it.key()->uuid();
continue; 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<AbstractWaylandOutput::Transform>(changeset->transform());
props->overscan = changeset->overscan();
props->rgbRange = static_cast<AbstractWaylandOutput::RgbRange>(changeset->rgbRange());
props->vrrPolicy = static_cast<RenderLoop::VrrPolicy>(changeset->vrrPolicy());
}
qDebug(KWIN_CORE) << "Platform::requestOutputsChange enabling" << changeset << it.key()->uuid() << changeset->enabledChanged() << changeset->enabled(); const auto outputs = enabledOutputs();
bool allDisabled = !std::any_of(outputs.begin(), outputs.end(), [&cfg](const auto &output){
if (changeset->enabledChanged() && auto o = qobject_cast<AbstractWaylandOutput*>(output);
changeset->enabled()) { if (!o) {
output->setEnabled(true); qCWarning(KWIN_CORE) << "Platform::requestOutputsChange should only be called for Wayland platforms!";
return false;
} }
return cfg.changeSet(o)->enabled;
output->applyChanges(changeset); });
if (allDisabled) {
qCWarning(KWIN_CORE) << "Disabling all outputs through configuration changes is not allowed";
config->setFailed();
return;
} }
//process any disable requests if (applyOutputChanges(cfg)) {
for (auto it = changes.begin(); it != changes.end(); it++) { if (config->primaryChanged()) {
const KWaylandServer::OutputChangeSetV2 *changeset = it.value(); setPrimaryOutput(findOutput(config->primary()->uuid()));
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);
} }
Q_EMIT screens()->changed();
config->setApplied();
} else {
qCDebug(KWIN_CORE) << "Applying config failed";
config->setFailed();
} }
}
if (config->primaryChanged()) { bool Platform::applyOutputChanges(const WaylandOutputConfig &config)
setPrimaryOutput(findOutput(config->primary()->uuid())); {
const auto outputs = enabledOutputs();
for (const auto &output : outputs) {
static_cast<AbstractWaylandOutput*>(output)->applyChanges(config);
} }
return true;
Q_EMIT screens()->changed();
config->setApplied();
} }
AbstractOutput *Platform::findOutput(int screenId) const AbstractOutput *Platform::findOutput(int screenId) const

View file

@ -42,6 +42,7 @@ class Scene;
class ScreenEdges; class ScreenEdges;
class Session; class Session;
class Toplevel; class Toplevel;
class WaylandOutputConfig;
class KWIN_EXPORT Outputs : public QVector<AbstractOutput*> class KWIN_EXPORT Outputs : public QVector<AbstractOutput*>
{ {
@ -521,6 +522,11 @@ protected:
virtual void doShowCursor(); virtual void doShowCursor();
virtual void doSetSoftwareCursor(); virtual void doSetSoftwareCursor();
/**
* Applies the output changes. Default implementation only sets values common between platforms
*/
virtual bool applyOutputChanges(const WaylandOutputConfig &config);
private: private:
void triggerCursorRepaint(); void triggerCursorRepaint();
bool m_softwareCursor = false; bool m_softwareCursor = false;

View file

@ -0,0 +1,39 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2021 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "waylandoutputconfig.h"
namespace KWin
{
QSharedPointer<OutputChangeSet> WaylandOutputConfig::changeSet(AbstractWaylandOutput *output)
{
const auto ptr = constChangeSet(output);
m_properties[output] = ptr;
return ptr;
}
QSharedPointer<OutputChangeSet> WaylandOutputConfig::constChangeSet(AbstractWaylandOutput *output) const
{
if (!m_properties.contains(output)) {
auto props = QSharedPointer<OutputChangeSet>::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];
}
}

44
src/waylandoutputconfig.h Normal file
View file

@ -0,0 +1,44 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2021 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QPoint>
#include <QSize>
#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<OutputChangeSet> changeSet(AbstractWaylandOutput *output);
QSharedPointer<OutputChangeSet> constChangeSet(AbstractWaylandOutput *output) const;
private:
QMap<AbstractWaylandOutput*, QSharedPointer<OutputChangeSet>> m_properties;
};
}