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
waylandclient.cpp
waylandoutput.cpp
waylandoutputconfig.cpp
waylandoutputdevicev2.cpp
waylandshellintegration.cpp
window_property_notify_x11_filter.cpp

View file

@ -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;

View file

@ -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.
*/

View file

@ -9,6 +9,7 @@
*/
#include "abstract_wayland_output.h"
#include "screens.h"
#include "waylandoutputconfig.h"
// KWayland
#include <KWaylandServer/outputchangeset_v2.h>
@ -20,11 +21,6 @@
namespace KWin
{
static AbstractWaylandOutput::Transform outputDeviceTransformToKWinTransform(KWaylandServer::OutputDeviceV2Interface::Transform transform)
{
return static_cast<AbstractWaylandOutput::Transform>(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<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();
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) {

View file

@ -18,14 +18,11 @@
#include <QTimer>
#include <QSize>
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);
}

View file

@ -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 <gbm.h>
@ -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<DrmAbstractOutput*> &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<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 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<DrmAbstractOutput*> &outputs);
void handleUdevEvent();
DrmGpu *addGpu(const QString &fileName);

View file

@ -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<drmModeRes> 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<DrmConnector *> 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<DrmOutput*, DrmPipeline*> 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);
}
m_pipelines.removeOne(output->pipeline());
oldPipelines.insert(output, output->pipeline());
output->setPipeline(nullptr);
}
} else {
for (const auto &pipeline : qAsConst(m_pipelines)) {
pipeline->revertPendingChanges();
}
}
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) {
// 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<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;
// 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<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) {
// 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<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;
}
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<DrmLeaseOutput*>(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<DrmAbstractOutput*> DrmGpu::outputs() const

View file

@ -69,6 +69,7 @@ public:
QVector<DrmAbstractOutput*> outputs() const;
const QVector<DrmPipeline*> 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<DrmPipeline *> findWorkingCombination(const QVector<DrmPipeline *> &pipelines, QVector<DrmConnector *> connectors, QVector<DrmCrtc *> crtcs);
bool commitCombination(const QVector<DrmPipeline *> &pipelines);
bool checkCrtcAssignment(QVector<DrmConnector*> connectors, QVector<DrmCrtc*> crtcs);
void handleLeaseRequest(KWaylandServer::DrmLeaseV1Interface *leaseRequest);
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();
}
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";
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;
}
}

View file

@ -31,7 +31,7 @@ public:
DrmLeaseOutput(DrmPipeline *pipeline, KWaylandServer::DrmLeaseDeviceV1Interface *leaseDevice);
~DrmLeaseOutput() override;
void addLeaseObjects(QVector<uint32_t> &objectList);
bool addLeaseObjects(QVector<uint32_t> &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;

View file

@ -11,6 +11,7 @@
#include "drm_gpu.h"
#include "drm_pointer.h"
#include "logging.h"
#include "drm_pipeline.h"
#include <main.h>
// 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);

View file

@ -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<DrmPipeline> m_pipeline;
DrmScopedPointer<drmModeConnector> m_conn;
QVector<uint32_t> m_encoders;
Edid m_edid;

View file

@ -21,6 +21,7 @@
#include "renderloop_p.h"
#include "screens.h"
#include "session.h"
#include "waylandoutputconfig.h"
// Qt
#include <QMatrix4x4>
#include <QCryptographicHash>
@ -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<DrmDumbBuffer>::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;
}
}
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!";
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->setTransformation(DrmPlane::Transformation::Rotate0);
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!";
}
}
// 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();
}
}
@ -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,9 +383,15 @@ bool DrmOutput::present(const QSharedPointer<DrmBuffer> &buffer, QRegion damaged
return false;
}
RenderLoopPrivate *renderLoopPrivate = RenderLoopPrivate::get(m_renderLoop);
if (!m_pipeline->setSyncMode(renderLoopPrivate->presentMode)) {
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;
Q_EMIT outputChange(damagedRegion);
@ -403,27 +403,22 @@ bool DrmOutput::present(const QSharedPointer<DrmBuffer> &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<DrmGammaRamp>::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<uint64_t> 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);
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::applyQueuedChanges(const WaylandOutputConfig &config)
{
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::setPipeline(DrmPipeline *pipeline)
void DrmOutput::revertQueuedChanges()
{
Q_ASSERT_X(!pipeline || pipeline->connector() == m_connector, "DrmOutput::setPipeline", "Pipeline with wrong connector set!");
m_pipeline = pipeline;
m_pipeline->revertPendingChanges();
}
}

View file

@ -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<uint64_t> 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<AbstractWaylandOutput::Mode> 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;

View file

@ -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<DrmBuffer> &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<DrmBuffer> &buffer)
return true;
}
bool DrmPipeline::atomicCommit()
{
return commitPipelines({this}, CommitMode::Commit);
}
bool DrmPipeline::commitPipelines(const QVector<DrmPipeline*> &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<DrmPipeline*> &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<DrmPipeline*> &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)) {
if (!pipeline->applyPendingChangesLegacy()) {
failure = true;
break;
}
}
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<DrmPipeline*> *userData = new QVector<DrmPipeline*>();
*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<DrmBuffer> buffer;
// try to re-use buffers if possible.
const auto &checkBuffer = [this, backend, &buffer](const QSharedPointer<DrmBuffer> &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<DrmGbmBuffer>::create(m_gpu, bo, nullptr);
buffer = QSharedPointer<DrmGbmBuffer>::create(gpu(), bo, nullptr);
#endif
} else {
buffer = QSharedPointer<DrmDumbBuffer>::create(m_gpu, sourceSize());
buffer = QSharedPointer<DrmDumbBuffer>::create(gpu(), sourceSize());
}
if (buffer && buffer->bufferId()) {
m_oldTestBuffer = m_primaryBuffer;
@ -315,175 +255,117 @@ bool DrmPipeline::checkTestBuffer()
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)
{
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);
}
}
if (!success) {
qCWarning(KWIN_DRM) << "Setting active to" << active << "failed" << strerror(errno);
}
if (isActive()) {
// enable cursor (again)
m_crtc->setLegacyCursor();
}
return success;
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);
}
bool DrmPipeline::setGammaRamp(const GammaRamp &ramp)
{
// 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];
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 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);
void DrmPipeline::applyPendingChanges()
{
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<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);
return false;
}
pending.crtc->setLegacyCursor();
}
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::legacyModeset()
{
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_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 {
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;
}
pending.crtc->setCurrent(m_primaryBuffer);
}
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;
}
if (!m_gpu->atomicModeSetting()) {
return false;
}
if (!m_primaryPlane->setTransformation(transformation)) {
return false;
}
return true;
}
bool DrmPipeline::setSyncMode(RenderLoopPrivate::SyncMode syncMode)
{
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())) {
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();
} else {
return false;
}
}
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<uint64_t> 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());
}
}
}

View file

@ -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<DrmBuffer> &buffer);
bool modeset(int modeIndex);
bool setCursor(const QSharedPointer<DrmDumbBuffer> &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<DrmDumbBuffer> &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<DrmGammaRamp> 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<DrmBuffer> m_primaryBuffer;
QSharedPointer<DrmBuffer> m_oldTestBuffer;
bool m_legacyNeedsModeset = true;
QVector<DrmObject*> m_allObjects;
QMap<uint32_t, QVector<uint64_t>> 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;
};
}

View file

@ -70,6 +70,13 @@ public:
bool needsCommit() const;
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:
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)
{
setDpmsModeInternal(mode);

View file

@ -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;

View file

@ -307,7 +307,7 @@ bool EglStreamBackend::resetOutput(Output &o)
if (isPrimary()) {
// dumb buffer used for modesetting
o.buffer = QSharedPointer<DrmDumbBuffer>::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();

View file

@ -23,6 +23,7 @@
#include "screenedge.h"
#include "touch_input.h"
#include "wayland_server.h"
#include "waylandoutputconfig.h"
#include <KWaylandServer/outputconfiguration_v2_interface.h>
#include <KWaylandServer/outputchangeset_v2.h>
@ -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<AbstractWaylandOutput*>(findOutput(it.key()->uuid()));
if (!output) {
qCWarning(KWIN_CORE) << "Could NOT find output matching " << it.key()->uuid();
continue;
}
qDebug(KWIN_CORE) << "Platform::requestOutputsChange enabling" << changeset << it.key()->uuid() << changeset->enabledChanged() << changeset->enabled();
if (changeset->enabledChanged() &&
changeset->enabled()) {
output->setEnabled(true);
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());
}
output->applyChanges(changeset);
}
//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);
}
const auto outputs = enabledOutputs();
bool allDisabled = !std::any_of(outputs.begin(), outputs.end(), [&cfg](const auto &output){
auto o = qobject_cast<AbstractWaylandOutput*>(output);
if (!o) {
qCWarning(KWIN_CORE) << "Platform::requestOutputsChange should only be called for Wayland platforms!";
return false;
}
return cfg.changeSet(o)->enabled;
});
if (allDisabled) {
qCWarning(KWIN_CORE) << "Disabling all outputs through configuration changes is not allowed";
config->setFailed();
return;
}
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();
}
}
bool Platform::applyOutputChanges(const WaylandOutputConfig &config)
{
const auto outputs = enabledOutputs();
for (const auto &output : outputs) {
static_cast<AbstractWaylandOutput*>(output)->applyChanges(config);
}
return true;
}
AbstractOutput *Platform::findOutput(int screenId) const

View file

@ -42,6 +42,7 @@ class Scene;
class ScreenEdges;
class Session;
class Toplevel;
class WaylandOutputConfig;
class KWIN_EXPORT Outputs : public QVector<AbstractOutput*>
{
@ -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;

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