platforms/drm: Introduce DrmPipeline

DrmPipeline is what now contains all the drm bits related to
modesetting and presentation, instead of that being in DrmOutput.
This gives a lot more freedom for managing drm resources and
enables far better usage of the atomic API with guaranteed
immutability for failed tests.
This commit is contained in:
Xaver Hugl 2021-03-27 15:01:34 +01:00
parent b68cd3110a
commit 5a22deda3b
14 changed files with 766 additions and 678 deletions

View file

@ -13,6 +13,7 @@ set(DRM_SOURCES
drm_gpu.cpp
egl_multi_backend.cpp
abstract_egl_drm_backend.cpp
drm_pipeline.cpp
)
if (HAVE_GBM)

View file

@ -155,11 +155,13 @@ void DrmBackend::reactivate()
if (!usesSoftwareCursor()) {
for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) {
DrmOutput *o = *it;
// only relevant in atomic mode
o->m_modesetRequested = true;
o->m_crtc->blank(o);
o->showCursor();
o->moveCursor();
if (o->isEnabled()) {
o->updateMode(o->connector()->currentModeIndex());
o->showCursor();
o->moveCursor();
} else {
o->updateEnablement(false);
}
}
}
@ -178,7 +180,6 @@ void DrmBackend::deactivate()
}
for (DrmOutput *output : qAsConst(m_outputs)) {
output->hideCursor();
output->renderLoop()->inhibit();
}
@ -431,15 +432,16 @@ QString DrmBackend::generateOutputConfigurationUuid() const
void DrmBackend::enableOutput(DrmOutput *output, bool enable)
{
bool enabled = m_enabledOutputs.contains(output);
if (enabled == enable) {
return;
}
if (enable) {
Q_ASSERT(!m_enabledOutputs.contains(output));
m_enabledOutputs << output;
emit output->gpu()->outputEnabled(output);
emit outputEnabled(output);
} else {
Q_ASSERT(m_enabledOutputs.contains(output));
m_enabledOutputs.removeOne(output);
Q_ASSERT(!m_enabledOutputs.contains(output));
emit output->gpu()->outputDisabled(output);
emit outputDisabled(output);
}
@ -520,7 +522,11 @@ void DrmBackend::updateCursor()
qCDebug(KWIN_DRM) << "Failed to show cursor on output" << output->name();
break;
}
output->moveCursor();
success = output->moveCursor();
if (!success) {
qCDebug(KWIN_DRM) << "Failed to move cursor on output" << output->name();
break;
}
}
setSoftwareCursor(!success);

View file

@ -259,12 +259,10 @@ bool DrmGpu::updateOutputs()
DrmOutput *output = new DrmOutput(this->m_backend, this);
output->m_conn = con;
output->m_crtc = crtc;
output->m_mode = connector->modes[0];
output->m_primaryPlane = getCompatiblePlane(DrmPlane::TypeIndex::Primary, crtc);
output->m_cursorPlane = getCompatiblePlane(DrmPlane::TypeIndex::Cursor, crtc);
qCDebug(KWIN_DRM) << "For new output use mode " << output->m_mode.name << output->m_mode.hdisplay << output->m_mode.vdisplay;
if (!output->init(connector.data())) {
qCDebug(KWIN_DRM) << "For new output use mode" << con->currentMode().mode.name;
if (!output->init()) {
qCWarning(KWIN_DRM) << "Failed to create output for connector " << con->id();
delete output;
continue;
@ -413,4 +411,20 @@ void DrmGpu::dispatchEvents()
drmHandleEvent(m_fd, &context);
}
QSharedPointer<DrmBuffer> DrmGpu::createTestbuffer(const QSize &size)
{
#if HAVE_GBM
if (m_gbmDevice) {
gbm_bo *bo = gbm_bo_create(m_gbmDevice, size.width(), size.height(), GBM_FORMAT_XRGB8888, GBM_BO_USE_SCANOUT);
if (bo) {
auto buffer = QSharedPointer<DrmGbmBuffer>::create(this, bo, nullptr);
if (buffer->bufferId()) {
return buffer;
}
}
}
#endif
return QSharedPointer<DrmDumbBuffer>::create(this, size);
}
}

View file

@ -98,6 +98,8 @@ public:
void waitIdle();
QSharedPointer<DrmBuffer> createTestbuffer(const QSize &size);
Q_SIGNALS:
void outputAdded(DrmOutput *output);
void outputRemoved(DrmOutput *output);

View file

@ -3,6 +3,7 @@
This file is part of the KDE project.
SPDX-FileCopyrightText: 2016 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2021 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
@ -29,8 +30,31 @@ DrmConnector::DrmConnector(DrmGpu *gpu, uint32_t connectorId)
DrmConnector::~DrmConnector() = default;
namespace {
quint64 refreshRateForMode(_drmModeModeInfo *m)
{
// Calculate higher precision (mHz) refresh rate
// logic based on Weston, see compositor-drm.c
quint64 refreshRate = (m->clock * 1000000LL / m->htotal + m->vtotal / 2) / m->vtotal;
if (m->flags & DRM_MODE_FLAG_INTERLACE) {
refreshRate *= 2;
}
if (m->flags & DRM_MODE_FLAG_DBLSCAN) {
refreshRate /= 2;
}
if (m->vscan > 1) {
refreshRate /= m->vscan;
}
return refreshRate;
}
}
bool DrmConnector::init()
{
if (!m_conn->count_modes) {
return false;
}
qCDebug(KWIN_DRM) << "Creating connector" << id();
if (!initProps({
@ -72,16 +96,26 @@ bool DrmConnector::init()
m_physicalSize = overwriteSize;
}
// init modes
for (int i = 0; i < m_conn->count_modes; i++) {
auto mode = m_conn->modes[i];
Mode m;
m.mode = mode;
m.size = QSize(mode.hdisplay, mode.vdisplay);
m.refreshRate = refreshRateForMode(&mode);
m_modes << m;
}
return true;
}
bool DrmConnector::isConnected()
{
DrmScopedPointer<drmModeConnector> con(drmModeGetConnector(gpu()->fd(), id()));
if (!con) {
m_conn.reset(drmModeGetConnector(gpu()->fd(), id()));
if (!m_conn) {
return false;
}
return con->connection == DRM_MODE_CONNECTED;
return m_conn->connection == DRM_MODE_CONNECTED;
}
static QHash<int, QByteArray> s_connectorNames = {
@ -132,5 +166,46 @@ QSize DrmConnector::physicalSize() const
return m_physicalSize;
}
const DrmConnector::Mode &DrmConnector::currentMode() const
{
return m_modes[m_modeIndex];
}
int DrmConnector::currentModeIndex() const
{
return m_modeIndex;
}
const QVector<DrmConnector::Mode> &DrmConnector::modes()
{
return m_modes;
}
void DrmConnector::setModeIndex(int index)
{
m_modeIndex = index;
}
AbstractWaylandOutput::SubPixel DrmConnector::subpixel() const
{
switch (m_conn->subpixel) {
case DRM_MODE_SUBPIXEL_UNKNOWN:
return AbstractWaylandOutput::SubPixel::Unknown;
case DRM_MODE_SUBPIXEL_NONE:
return AbstractWaylandOutput::SubPixel::None;
case DRM_MODE_SUBPIXEL_HORIZONTAL_RGB:
return AbstractWaylandOutput::SubPixel::Horizontal_RGB;
case DRM_MODE_SUBPIXEL_HORIZONTAL_BGR:
return AbstractWaylandOutput::SubPixel::Horizontal_BGR;
case DRM_MODE_SUBPIXEL_VERTICAL_RGB:
return AbstractWaylandOutput::SubPixel::Vertical_RGB;
case DRM_MODE_SUBPIXEL_VERTICAL_BGR:
return AbstractWaylandOutput::SubPixel::Vertical_BGR;
default:
Q_UNREACHABLE();
}
}
}

View file

@ -3,16 +3,21 @@
This file is part of the KDE project.
SPDX-FileCopyrightText: 2016 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2021 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef KWIN_DRM_OBJECT_CONNECTOR_H
#define KWIN_DRM_OBJECT_CONNECTOR_H
#pragma once
#include <QPoint>
#include <QSize>
#include <QSize>
#include "drm_object.h"
#include "edid.h"
#include "drm_pointer.h"
#include "abstract_wayland_output.h"
namespace KWin
{
@ -61,15 +66,31 @@ public:
bool isInternal() const;
QSize physicalSize() const;
struct Mode {
drmModeModeInfo mode;
QSize size;
uint32_t refreshRate;
};
/**
* until the actual current mode is set with setMode(int)
* this will always return the first mode
*/
const Mode &currentMode() const;
int currentModeIndex() const;
const QVector<Mode> &modes();
void setModeIndex(int index);
AbstractWaylandOutput::SubPixel subpixel() const;
private:
DrmScopedPointer<drmModeConnector> m_conn;
QVector<uint32_t> m_encoders;
Edid m_edid;
QSize m_physicalSize = QSize(-1, -1);
QVector<Mode> m_modes;
int m_modeIndex = 0;
};
}
#endif

View file

@ -3,6 +3,7 @@
This file is part of the KDE project.
SPDX-FileCopyrightText: 2016 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2021 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
@ -33,6 +34,8 @@ bool DrmCrtc::init()
return initProps({
PropertyDefinition(QByteArrayLiteral("MODE_ID")),
PropertyDefinition(QByteArrayLiteral("ACTIVE")),
PropertyDefinition(QByteArrayLiteral("GAMMA_LUT")),
PropertyDefinition(QByteArrayLiteral("GAMMA_LUT_SIZE")),
}, DRM_MODE_OBJECT_CRTC);
}
@ -40,45 +43,12 @@ void DrmCrtc::flipBuffer()
{
m_currentBuffer = m_nextBuffer;
m_nextBuffer = nullptr;
delete m_blackBuffer;
m_blackBuffer = nullptr;
}
bool DrmCrtc::blank(DrmOutput *output)
drmModeModeInfo DrmCrtc::queryCurrentMode()
{
if (gpu()->atomicModeSetting()) {
return false;
}
if (!m_blackBuffer) {
DrmDumbBuffer *blackBuffer = new DrmDumbBuffer(gpu(), output->pixelSize());
if (!blackBuffer->map()) {
delete blackBuffer;
return false;
}
blackBuffer->image()->fill(Qt::black);
m_blackBuffer = blackBuffer;
}
if (output->setModeLegacy(m_blackBuffer)) {
m_currentBuffer = nullptr;
m_nextBuffer = nullptr;
return true;
}
return false;
}
bool DrmCrtc::setGammaRamp(const GammaRamp &gamma)
{
uint16_t *red = const_cast<uint16_t *>(gamma.red());
uint16_t *green = const_cast<uint16_t *>(gamma.green());
uint16_t *blue = const_cast<uint16_t *>(gamma.blue());
const bool isError = drmModeCrtcSetGamma(gpu()->fd(), id(),
gamma.size(), red, green, blue);
return !isError;
m_crtc.reset(drmModeGetCrtc(gpu()->fd(), id()));
return m_crtc->mode;
}
}

View file

@ -13,6 +13,8 @@
#include <QSharedPointer>
#include "drm_pointer.h"
namespace KWin
{
@ -31,7 +33,9 @@ public:
enum class PropertyIndex : uint32_t {
ModeId = 0,
Active,
Active = 1,
Gamma_LUT = 2,
Gamma_LUT_size = 3,
Count
};
@ -53,19 +57,26 @@ public:
}
void flipBuffer();
bool blank(DrmOutput *output);
int gammaRampSize() const {
if (const auto &prop = m_props.at(static_cast<int>(PropertyIndex::Gamma_LUT_size))) {
return prop->value();
}
return m_crtc->gamma_size;
}
bool setGammaRamp(const GammaRamp &gamma);
bool hasGammaProp() const {
return m_props.at(static_cast<int>(PropertyIndex::Gamma_LUT));
}
drmModeModeInfo queryCurrentMode();
private:
DrmScopedPointer<drmModeCrtc> m_crtc;
int m_pipeIndex;
QSharedPointer<DrmBuffer> m_currentBuffer;
QSharedPointer<DrmBuffer> m_nextBuffer;
DrmDumbBuffer *m_blackBuffer = nullptr;
int m_pipeIndex;
};
}

View file

@ -118,4 +118,30 @@ void DrmPlane::flipBuffer()
m_next = nullptr;
}
void DrmPlane::setScaled(const QSize &srcSize, const QSize &modeSize, int crtcId, bool enable)
{
QPoint targetPos = QPoint(0, 0);
QSize targetSize = modeSize;
if (modeSize != srcSize) {
targetSize = srcSize.scaled(modeSize, Qt::AspectRatioMode::KeepAspectRatio);
targetPos.setX((modeSize.width() - targetSize.width()) / 2);
targetPos.setY((modeSize.height() - targetSize.height()) / 2);
}
set(srcSize, targetPos, targetSize, crtcId, enable);
}
void DrmPlane::set(const QSize &src, const QPoint &dstPos, const QSize &dstSize, int crtcId, bool enable)
{
setValue(PropertyIndex::SrcX, 0);
setValue(PropertyIndex::SrcY, 0);
setValue(PropertyIndex::SrcW, src.width() << 16);
setValue(PropertyIndex::SrcH, src.height() << 16);
setValue(PropertyIndex::CrtcX, enable ? dstPos.x() : 0);
setValue(PropertyIndex::CrtcY, enable ? dstPos.y() : 0);
setValue(PropertyIndex::CrtcW, dstSize.width());
setValue(PropertyIndex::CrtcH, dstSize.height());
setValue(PropertyIndex::CrtcId, enable ? crtcId : 0);
setValue(PropertyIndex::FbId, (m_next && enable) ? m_next->bufferId() : 0);
}
}

View file

@ -90,6 +90,9 @@ public:
return m_supportedTransformations;
}
void setScaled(const QSize &srcSize, const QSize &modeSize, int crtcId, bool enable);
void set(const QSize &src, const QPoint &dstPos, const QSize &dstSize, int crtcId, bool enable);
private:
QSharedPointer<DrmBuffer> m_current;
QSharedPointer<DrmBuffer> m_next;

View file

@ -3,6 +3,7 @@
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2021 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
@ -29,6 +30,7 @@
#include <libdrm/drm_mode.h>
#include "drm_gpu.h"
#include "drm_pipeline.h"
namespace KWin
{
@ -45,6 +47,7 @@ DrmOutput::~DrmOutput()
{
Q_ASSERT(!m_pageFlipPending);
teardown();
delete m_pipeline;
}
RenderLoop *DrmOutput::renderLoop() const
@ -59,15 +62,12 @@ void DrmOutput::teardown()
}
m_deleted = true;
hideCursor();
m_crtc->blank(this);
if (m_primaryPlane) {
// TODO: when having multiple planes, also clean up these
m_primaryPlane->setCurrent(nullptr);
}
m_cursor[0].reset(nullptr);
m_cursor[1].reset(nullptr);
if (!m_pageFlipPending) {
deleteLater();
} //else will be deleted in the page flip handler
@ -82,35 +82,23 @@ void DrmOutput::releaseBuffers()
m_primaryPlane->setNext(nullptr);
}
bool DrmOutput::hideCursor()
bool DrmOutput::initCursor(const QSize &cursorSize)
{
return drmModeSetCursor(m_gpu->fd(), m_crtc->id(), 0, 0, 0) == 0;
m_cursor = QSharedPointer<DrmDumbBuffer>::create(m_gpu, cursorSize);
if (!m_cursor->map(QImage::Format_ARGB32_Premultiplied)) {
return false;
}
return updateCursor();
}
bool DrmOutput::showCursor(DrmDumbBuffer *c)
bool DrmOutput::hideCursor()
{
const QSize &s = c->size();
return drmModeSetCursor(m_gpu->fd(), m_crtc->id(), c->handle(), s.width(), s.height()) == 0;
return m_pipeline->setCursor(nullptr);
}
bool DrmOutput::showCursor()
{
if (m_deleted) {
return false;
}
const bool ret = showCursor(m_cursor[m_cursorIndex].data());
if (!ret) {
qCDebug(KWIN_DRM) << "DrmOutput::showCursor(DrmDumbBuffer) failed";
return ret;
}
if (m_hasNewCursor) {
m_cursorIndex = (m_cursorIndex + 1) % 2;
m_hasNewCursor = false;
}
return ret;
return m_pipeline->setCursor(m_cursor);
}
static bool isCursorSpriteCompatible(const QImage *buffer, const QImage *sprite)
@ -129,32 +117,31 @@ bool DrmOutput::updateCursor()
return false;
}
const Cursor *cursor = Cursors::self()->currentCursor();
if (!cursor) {
hideCursor();
return true;
}
const QImage cursorImage = cursor->image();
if (cursorImage.isNull()) {
return false;
hideCursor();
return true;
}
QImage *c = m_cursor[m_cursorIndex]->image();
QImage *c = m_cursor->image();
c->setDevicePixelRatio(scale());
if (!isCursorSpriteCompatible(c, &cursorImage)) {
// If the cursor image is too big, fall back to rendering the software cursor.
return false;
}
m_hasNewCursor = true;
c->fill(Qt::transparent);
QPainter p;
p.begin(c);
p.setWorldTransform(logicalToNativeMatrix(cursor->rect(), 1, transform()).toTransform());
p.drawImage(QPoint(0, 0), cursorImage);
p.end();
return true;
return m_pipeline->setCursor(m_cursor);
}
void DrmOutput::moveCursor()
bool DrmOutput::moveCursor()
{
Cursor *cursor = Cursors::self()->currentCursor();
const QMatrix4x4 hotspotMatrix = logicalToNativeMatrix(cursor->rect(), scale(), transform());
@ -163,89 +150,66 @@ void DrmOutput::moveCursor()
QPoint pos = monitorMatrix.map(cursor->pos());
pos -= hotspotMatrix.map(cursor->hotspot());
drmModeMoveCursor(m_gpu->fd(), m_crtc->id(), pos.x(), pos.y());
return m_pipeline->moveCursor(pos);
}
namespace {
quint64 refreshRateForMode(_drmModeModeInfo *m)
{
// Calculate higher precision (mHz) refresh rate
// logic based on Weston, see compositor-drm.c
quint64 refreshRate = (m->clock * 1000000LL / m->htotal + m->vtotal / 2) / m->vtotal;
if (m->flags & DRM_MODE_FLAG_INTERLACE) {
refreshRate *= 2;
}
if (m->flags & DRM_MODE_FLAG_DBLSCAN) {
refreshRate /= 2;
}
if (m->vscan > 1) {
refreshRate /= m->vscan;
}
return refreshRate;
}
}
static AbstractWaylandOutput::SubPixel drmSubPixelToKWinSubPixel(drmModeSubPixel subpixel)
{
switch (subpixel) {
case DRM_MODE_SUBPIXEL_UNKNOWN:
return AbstractWaylandOutput::SubPixel::Unknown;
case DRM_MODE_SUBPIXEL_NONE:
return AbstractWaylandOutput::SubPixel::None;
case DRM_MODE_SUBPIXEL_HORIZONTAL_RGB:
return AbstractWaylandOutput::SubPixel::Horizontal_RGB;
case DRM_MODE_SUBPIXEL_HORIZONTAL_BGR:
return AbstractWaylandOutput::SubPixel::Horizontal_BGR;
case DRM_MODE_SUBPIXEL_VERTICAL_RGB:
return AbstractWaylandOutput::SubPixel::Vertical_RGB;
case DRM_MODE_SUBPIXEL_VERTICAL_BGR:
return AbstractWaylandOutput::SubPixel::Vertical_BGR;
default:
Q_UNREACHABLE();
}
}
bool DrmOutput::init(drmModeConnector *connector)
bool DrmOutput::init()
{
if (m_gpu->atomicModeSetting() && !m_primaryPlane) {
return false;
}
setSubPixelInternal(drmSubPixelToKWinSubPixel(connector->subpixel));
setSubPixelInternal(m_conn->subpixel());
setInternal(m_conn->isInternal());
setCapabilityInternal(DrmOutput::Capability::Dpms);
initOutputDevice(connector);
setCapabilityInternal(Capability::Dpms);
initOutputDevice();
if (!m_gpu->atomicModeSetting() && !m_crtc->blank(this)) {
// We use legacy mode and the initial output blank failed.
return false;
}
m_pipeline = new DrmPipeline(this, m_gpu, m_conn, m_crtc, m_primaryPlane, m_cursorPlane);
updateMode(0);
// renderloop will be un-inhibited when updating DPMS
m_renderLoop->inhibit();
m_dpmsEnabled = false;
setDpmsMode(DpmsMode::On);
return true;
return m_dpmsEnabled;
}
void DrmOutput::initOutputDevice(drmModeConnector *connector)
static bool checkIfEqual(_drmModeModeInfo one, _drmModeModeInfo two) {
// for some reason a few values are 0
// thus they're ignored here
return one.clock == two.clock
&& one.hdisplay == two.hdisplay
&& one.hsync_start == two.hsync_start
&& one.hsync_end == two.hsync_end
&& one.htotal == two.htotal
&& one.hskew == two.hskew
&& one.vdisplay == two.vdisplay
&& one.vsync_start == two.vsync_start
&& one.vsync_end == two.vsync_end
&& one.vtotal == two.vtotal
&& one.vscan == two.vscan
&& one.vrefresh == two.vrefresh;
}
void DrmOutput::initOutputDevice()
{
// read in mode information
QVector<Mode> modes;
modes.reserve(connector->count_modes);
for (int i = 0; i < connector->count_modes; ++i) {
// TODO: in AMS here we could read and store for later every mode's blob_id
// would simplify isCurrentMode(..) and presentAtomically(..) in case of mode set
auto *m = &connector->modes[i];
auto modelist = m_conn->modes();
auto currentMode = m_crtc->queryCurrentMode();
for (int i = 0; i < modelist.count(); i++) {
const auto &m = modelist[i];
Mode mode;
if (isCurrentMode(m)) {
if (checkIfEqual(m.mode, currentMode)) {
mode.flags |= ModeFlag::Current;
m_conn->setModeIndex(i);
}
if (m->type & DRM_MODE_TYPE_PREFERRED) {
if (m.mode.type & DRM_MODE_TYPE_PREFERRED) {
mode.flags |= ModeFlag::Preferred;
}
mode.id = i;
mode.size = QSize(m->hdisplay, m->vdisplay);
mode.refreshRate = refreshRateForMode(m);
mode.size = m.size;
mode.refreshRate = m.refreshRate;
modes << mode;
}
@ -255,178 +219,50 @@ void DrmOutput::initOutputDevice(drmModeConnector *connector)
m_conn->physicalSize(), modes, m_conn->edid()->raw());
}
bool DrmOutput::isCurrentMode(const drmModeModeInfo *mode) const
{
return mode->clock == m_mode.clock
&& mode->hdisplay == m_mode.hdisplay
&& mode->hsync_start == m_mode.hsync_start
&& mode->hsync_end == m_mode.hsync_end
&& mode->htotal == m_mode.htotal
&& mode->hskew == m_mode.hskew
&& mode->vdisplay == m_mode.vdisplay
&& mode->vsync_start == m_mode.vsync_start
&& mode->vsync_end == m_mode.vsync_end
&& mode->vtotal == m_mode.vtotal
&& mode->vscan == m_mode.vscan
&& mode->vrefresh == m_mode.vrefresh
&& mode->flags == m_mode.flags
&& mode->type == m_mode.type
&& qstrcmp(mode->name, m_mode.name) == 0;
}
bool DrmOutput::initCursor(const QSize &cursorSize)
{
auto createCursor = [this, cursorSize] (int index) {
m_cursor[index].reset(new DrmDumbBuffer(m_gpu, cursorSize));
if (!m_cursor[index]->map(QImage::Format_ARGB32_Premultiplied)) {
return false;
}
return true;
};
if (!createCursor(0) || !createCursor(1)) {
return false;
}
return true;
}
void DrmOutput::updateEnablement(bool enable)
{
if (enable) {
m_dpmsModePending = DpmsMode::On;
if (m_gpu->atomicModeSetting()) {
atomicEnable();
} else {
if (dpmsLegacyApply()) {
m_backend->enableOutput(this, true);
}
}
if (m_dpmsEnabled == enable) {
m_backend->enableOutput(this, enable);
return;
}
if (m_pipeline->setEnablement(enable)) {
m_backend->enableOutput(this, enable);
if (enable) {
m_renderLoop->inhibit();
} else {
m_renderLoop->uninhibit();
}
m_dpmsEnabled = enable;
} else {
m_dpmsModePending = DpmsMode::Off;
if (m_gpu->atomicModeSetting()) {
atomicDisable();
} else {
if (dpmsLegacyApply()) {
m_backend->enableOutput(this, false);
}
}
}
}
void DrmOutput::atomicEnable()
{
m_modesetRequested = true;
if (m_atomicOffPending) {
Q_ASSERT(m_pageFlipPending);
m_atomicOffPending = false;
}
m_backend->enableOutput(this, true);
dpmsFinishOn();
if (Compositor *compositor = Compositor::self()) {
compositor->addRepaintFull();
}
}
void DrmOutput::atomicDisable()
{
m_modesetRequested = true;
m_backend->enableOutput(this, false);
m_atomicOffPending = true;
if (!m_pageFlipPending) {
dpmsAtomicOff();
qCWarning(KWIN_DRM) << "Setting enablement to" << enable << "failed!" << strerror(errno);
}
}
void DrmOutput::setDpmsMode(DpmsMode mode)
{
if (!m_conn->dpms() || !isEnabled()) {
bool newDpmsEnable = mode == DpmsMode::On;
if (newDpmsEnable == m_dpmsEnabled) {
qCDebug(KWIN_DRM) << "New DPMS mode equals old mode. DPMS unchanged.";
AbstractWaylandOutput::setDpmsModeInternal(mode);
return;
}
if (mode == m_dpmsModePending) {
return;
}
m_dpmsModePending = mode;
if (m_gpu->atomicModeSetting()) {
m_modesetRequested = true;
if (mode == DpmsMode::On) {
if (m_atomicOffPending) {
Q_ASSERT(m_pageFlipPending);
m_atomicOffPending = false;
if (m_pipeline->setEnablement(newDpmsEnable)) {
m_dpmsEnabled = newDpmsEnable;
AbstractWaylandOutput::setDpmsModeInternal(mode);
if (m_dpmsEnabled) {
m_renderLoop->uninhibit();
if (Compositor *compositor = Compositor::self()) {
compositor->addRepaintFull();
}
dpmsFinishOn();
} else {
m_atomicOffPending = true;
if (!m_pageFlipPending) {
dpmsAtomicOff();
}
m_renderLoop->inhibit();
}
} else {
dpmsLegacyApply();
qCWarning(KWIN_DRM) << "Setting DPMS failed!" << strerror(errno);
}
}
void DrmOutput::dpmsFinishOn()
{
qCDebug(KWIN_DRM) << "DPMS mode set for output" << m_crtc->id() << "to On.";
m_backend->checkOutputsAreOn();
m_crtc->blank(this);
m_renderLoop->uninhibit();
if (Compositor *compositor = Compositor::self()) {
compositor->addRepaintFull();
}
}
void DrmOutput::dpmsFinishOff()
{
qCDebug(KWIN_DRM) << "DPMS mode set for output" << m_crtc->id() << "to Off.";
if (isEnabled()) {
m_backend->createDpmsFilter();
}
m_renderLoop->inhibit();
}
static uint64_t kwinDpmsModeToDrmDpmsMode(AbstractWaylandOutput::DpmsMode dpmsMode)
{
switch (dpmsMode) {
case AbstractWaylandOutput::DpmsMode::On:
return DRM_MODE_DPMS_ON;
case AbstractWaylandOutput::DpmsMode::Standby:
return DRM_MODE_DPMS_STANDBY;
case AbstractWaylandOutput::DpmsMode::Suspend:
return DRM_MODE_DPMS_SUSPEND;
case AbstractWaylandOutput::DpmsMode::Off:
return DRM_MODE_DPMS_OFF;
default:
Q_UNREACHABLE();
}
}
bool DrmOutput::dpmsLegacyApply()
{
if (drmModeConnectorSetProperty(m_gpu->fd(), m_conn->id(),
m_conn->dpms()->propId(),
kwinDpmsModeToDrmDpmsMode(m_dpmsModePending)) < 0) {
m_dpmsModePending = dpmsMode();
qCWarning(KWIN_DRM) << "Setting DPMS failed";
return false;
}
if (m_dpmsModePending == DpmsMode::On) {
dpmsFinishOn();
} else {
dpmsFinishOff();
}
setDpmsModeInternal(m_dpmsModePending);
return true;
}
DrmPlane::Transformations outputToPlaneTransform(DrmOutput::Transform transform)
{
using OutTrans = DrmOutput::Transform;
@ -472,7 +308,7 @@ void DrmOutput::updateTransform(Transform transform)
|| transform == Transform::Flipped90
|| transform == Transform::Rotated270
|| transform == Transform::Flipped270;
const auto &currentTransform = m_primaryPlane->transformation();
if (!qEnvironmentVariableIsSet("KWIN_DRM_SW_ROTATIONS_ONLY") &&
(m_primaryPlane->supportedTransformations() & planeTransform) &&
!isPortrait) {
@ -480,27 +316,28 @@ void DrmOutput::updateTransform(Transform transform)
} else {
m_primaryPlane->setTransformation(DrmPlane::Transformation::Rotate0);
}
if (!m_pipeline->test()) {
m_primaryPlane->setTransformation(currentTransform);
}
}
m_modesetRequested = true;
// show cursor only if is enabled, i.e if pointer device is presentP
if (!m_backend->isCursorHidden() && !m_backend->usesSoftwareCursor()) {
// the cursor might need to get rotated
updateCursor();
showCursor();
updateCursor();
}
}
void DrmOutput::updateMode(uint32_t width, uint32_t height, uint32_t refreshRate)
{
if (m_mode.hdisplay == width && m_mode.vdisplay == height && m_mode.vrefresh == refreshRate) {
if (m_conn->currentMode().size == QSize(width, height) && m_conn->currentMode().refreshRate == refreshRate) {
return;
}
// try to find a fitting mode
DrmScopedPointer<drmModeConnector> connector(drmModeGetConnectorCurrent(m_gpu->fd(), m_conn->id()));
for (int i = 0; i < connector->count_modes; i++) {
auto mode = connector->modes[i];
if (mode.hdisplay == width && mode.vdisplay == height && mode.vrefresh == refreshRate) {
auto modelist = m_conn->modes();
for (int i = 0; i < modelist.count(); i++) {
if (modelist[i].size == QSize(width, height) && modelist[i].refreshRate == refreshRate) {
updateMode(i);
return;
}
@ -511,339 +348,69 @@ void DrmOutput::updateMode(uint32_t width, uint32_t height, uint32_t refreshRate
void DrmOutput::updateMode(int modeIndex)
{
// get all modes on the connector
DrmScopedPointer<drmModeConnector> connector(drmModeGetConnector(m_gpu->fd(), m_conn->id()));
if (connector->count_modes <= modeIndex) {
// TODO: error?
const auto &modelist = m_conn->modes();
if (modeIndex >= modelist.count()) {
qCWarning(KWIN_DRM, "Mode %d for output %s doesn't exist", modeIndex, qPrintable(uuid().toString()));
return;
}
if (isCurrentMode(&connector->modes[modeIndex])) {
// nothing to do
return;
const auto &mode = modelist[modeIndex];
QSize srcSize = mode.size;
if (hardwareTransforms() &&
(transform() == Transform::Rotated270
|| transform() == Transform::Rotated90
|| transform() == Transform::Flipped90
|| transform() == Transform::Flipped270)) {
srcSize = srcSize.transposed();
}
m_mode = connector->modes[modeIndex];
m_modesetRequested = true;
setCurrentModeInternal();
}
void DrmOutput::setCurrentModeInternal()
{
AbstractWaylandOutput::setCurrentModeInternal(QSize(m_mode.hdisplay, m_mode.vdisplay),
refreshRateForMode(&m_mode));
if (m_pipeline->modeset(srcSize, mode.mode)) {
m_conn->setModeIndex(modeIndex);
qCDebug(KWIN_DRM, "Modeset successful on %s (%dx%d@%f)", qPrintable(name()), modeSize().width(), modeSize().height(), refreshRate() * 0.001);
} else {
qCWarning(KWIN_DRM, "Modeset failed on %s (%dx%d@%f)! %s", qPrintable(name()), modeSize().width(), modeSize().height(), refreshRate() * 0.001, strerror(errno));
}
// this must be done even if it fails, for the initial modeset
const auto &currentMode = m_conn->currentMode();
AbstractWaylandOutput::setCurrentModeInternal(currentMode.size, currentMode.refreshRate);
m_renderLoop->setRefreshRate(currentMode.refreshRate);
}
void DrmOutput::pageFlipped()
{
// In legacy mode we might get a page flip through a blank.
Q_ASSERT(m_pageFlipPending || !m_gpu->atomicModeSetting());
m_pageFlipPending = false;
if (m_deleted) {
qCDebug(KWIN_DRM) << "delete-pageflip";
deleteLater();
return;
}
if (!m_crtc) {
return;
}
if (m_gpu->atomicModeSetting()) {
for (DrmPlane *p : m_nextPlanesFlipList) {
p->flipBuffer();
m_primaryPlane->flipBuffer();
if (m_cursorPlane) {
m_cursorPlane->flipBuffer();
}
m_nextPlanesFlipList.clear();
} else {
m_crtc->flipBuffer();
}
if (m_atomicOffPending) {
dpmsAtomicOff();
}
}
bool DrmOutput::present(const QSharedPointer<DrmBuffer> &buffer)
{
if (!buffer || buffer->bufferId() == 0) {
return false;
}
if (m_dpmsModePending != DpmsMode::On) {
return false;
}
return m_gpu->atomicModeSetting() ? presentAtomically(buffer) : presentLegacy(buffer);
}
bool DrmOutput::dpmsAtomicOff()
{
m_atomicOffPending = false;
// TODO: With multiple planes: deactivate all of them here
m_primaryPlane->setNext(nullptr);
m_nextPlanesFlipList << m_primaryPlane;
if (!doAtomicCommit(AtomicCommitMode::Test)) {
qCDebug(KWIN_DRM) << "Atomic test commit to Dpms Off failed. Aborting.";
return false;
}
if (!doAtomicCommit(AtomicCommitMode::Real)) {
qCDebug(KWIN_DRM) << "Atomic commit to Dpms Off failed. This should have never happened! Aborting.";
return false;
}
m_nextPlanesFlipList.clear();
dpmsFinishOff();
return true;
}
bool DrmOutput::presentAtomically(const QSharedPointer<DrmBuffer> &buffer)
{
if (!m_backend->session()->isActive()) {
qCWarning(KWIN_DRM) << "Refusing to present output because session is inactive";
qCWarning(KWIN_DRM) << "session not active!";
return false;
}
if (m_pageFlipPending) {
qCWarning(KWIN_DRM) << "Page not yet flipped.";
qCWarning(KWIN_DRM) << "page not flipped yet!";
return false;
}
#if HAVE_EGL_STREAMS
if (m_gpu->useEglStreams() && !m_modesetRequested) {
// EglStreamBackend queues normal page flips through EGL,
// modesets are still performed through DRM-KMS
if (m_pipeline->present(buffer)) {
m_pageFlipPending = true;
return true;
}
#endif
m_primaryPlane->setNext(buffer);
m_nextPlanesFlipList << m_primaryPlane;
if (!doAtomicCommit(AtomicCommitMode::Test)) {
//TODO: When we use planes for layered rendering, fallback to renderer instead. Also for direct scanout?
//TODO: Probably should undo setNext and reset the flip list
qCDebug(KWIN_DRM) << "Atomic test commit failed. Aborting present.";
// go back to previous state
if (m_lastWorkingState.valid) {
m_mode = m_lastWorkingState.mode;
setTransformInternal(m_lastWorkingState.transform);
setGlobalPos(m_lastWorkingState.globalPos);
if (m_primaryPlane) {
m_primaryPlane->setTransformation(m_lastWorkingState.planeTransformations);
}
m_modesetRequested = true;
if (!m_backend->isCursorHidden()) {
// the cursor might need to get rotated
updateCursor();
showCursor();
}
setCurrentModeInternal();
emit screens()->changed();
}
return false;
}
const bool wasModeset = m_modesetRequested;
if (!doAtomicCommit(AtomicCommitMode::Real)) {
qCDebug(KWIN_DRM) << "Atomic commit failed. This should have never happened! Aborting present.";
//TODO: Probably should undo setNext and reset the flip list
return false;
}
if (wasModeset) {
// store current mode set as new good state
m_lastWorkingState.mode = m_mode;
m_lastWorkingState.transform = transform();
m_lastWorkingState.globalPos = globalPos();
if (m_primaryPlane) {
m_lastWorkingState.planeTransformations = m_primaryPlane->transformation();
}
m_lastWorkingState.valid = true;
m_renderLoop->setRefreshRate(refreshRateForMode(&m_mode));
}
m_pageFlipPending = true;
return true;
}
bool DrmOutput::presentLegacy(const QSharedPointer<DrmBuffer> &buffer)
{
if (m_crtc->next()) {
return false;
}
if (!m_backend->session()->isActive()) {
m_crtc->setNext(buffer);
return false;
}
// Do we need to set a new mode first?
if (!m_crtc->current() || m_crtc->current()->needsModeChange(buffer.get())) {
if (!setModeLegacy(buffer.get())) {
return false;
}
}
const bool ok = drmModePageFlip(m_gpu->fd(), m_crtc->id(), buffer->bufferId(), DRM_MODE_PAGE_FLIP_EVENT, this) == 0;
if (ok) {
m_crtc->setNext(buffer);
m_pageFlipPending = true;
} else {
qCWarning(KWIN_DRM) << "Page flip failed:" << strerror(errno);
}
return ok;
}
bool DrmOutput::setModeLegacy(DrmBuffer *buffer)
{
uint32_t connId = m_conn->id();
if (drmModeSetCrtc(m_gpu->fd(), m_crtc->id(), buffer->bufferId(), 0, 0, &connId, 1, &m_mode) == 0) {
return true;
} else {
qCWarning(KWIN_DRM) << "Mode setting failed";
qCCritical(KWIN_DRM) << "present failed! This never should've happened!";
return false;
}
}
bool DrmOutput::doAtomicCommit(AtomicCommitMode mode)
{
drmModeAtomicReq *req = drmModeAtomicAlloc();
auto errorHandler = [this, mode, req] () {
if (mode == AtomicCommitMode::Test) {
// TODO: when we later test overlay planes, make sure we change only the right stuff back
}
if (req) {
drmModeAtomicFree(req);
}
if (dpmsMode() != m_dpmsModePending) {
qCWarning(KWIN_DRM) << "Setting DPMS failed";
m_dpmsModePending = dpmsMode();
if (dpmsMode() != DpmsMode::On) {
dpmsFinishOff();
}
}
// TODO: see above, rework later for overlay planes!
for (DrmPlane *p : m_nextPlanesFlipList) {
p->setNext(nullptr);
}
m_nextPlanesFlipList.clear();
};
if (!req) {
qCWarning(KWIN_DRM) << "DRM: couldn't allocate atomic request";
errorHandler();
return false;
}
uint32_t flags = 0;
// Do we need to set a new mode?
if (m_modesetRequested) {
if (m_dpmsModePending == DpmsMode::On) {
if (drmModeCreatePropertyBlob(m_gpu->fd(), &m_mode, sizeof(m_mode), &m_blobId) != 0) {
qCWarning(KWIN_DRM) << "Failed to create property blob";
errorHandler();
return false;
}
}
if (!atomicReqModesetPopulate(req, m_dpmsModePending == DpmsMode::On)){
qCWarning(KWIN_DRM) << "Failed to populate Atomic Modeset";
errorHandler();
return false;
}
flags |= DRM_MODE_ATOMIC_ALLOW_MODESET;
}
if (mode == AtomicCommitMode::Real) {
if (m_dpmsModePending == DpmsMode::On) {
if (!(flags & DRM_MODE_ATOMIC_ALLOW_MODESET)) {
// TODO: Evaluating this condition should only be necessary, as long as we expect older kernels than 4.10.
flags |= DRM_MODE_ATOMIC_NONBLOCK;
}
#if HAVE_EGL_STREAMS
if (!m_gpu->useEglStreams())
// EglStreamBackend uses the NV_output_drm_flip_event EGL extension
// to register the flip event through eglStreamConsumerAcquireAttribNV
#endif
flags |= DRM_MODE_PAGE_FLIP_EVENT;
}
} else {
flags |= DRM_MODE_ATOMIC_TEST_ONLY;
}
bool ret = true;
// TODO: Make sure when we use more than one plane at a time, that we go through this list in the right order.
for (int i = m_nextPlanesFlipList.size() - 1; 0 <= i; i-- ) {
DrmPlane *p = m_nextPlanesFlipList[i];
ret &= p->atomicPopulate(req);
}
if (!ret) {
qCWarning(KWIN_DRM) << "Failed to populate atomic planes. Abort atomic commit!";
errorHandler();
return false;
}
if (drmModeAtomicCommit(m_gpu->fd(), req, flags, this)) {
qCDebug(KWIN_DRM) << "Atomic request failed to commit: " << strerror(errno);
errorHandler();
return false;
}
if (mode == AtomicCommitMode::Real && (flags & DRM_MODE_ATOMIC_ALLOW_MODESET)) {
qCDebug(KWIN_DRM) << "Atomic Modeset successful.";
m_modesetRequested = false;
setDpmsModeInternal(m_dpmsModePending);
}
drmModeAtomicFree(req);
return true;
}
bool DrmOutput::atomicReqModesetPopulate(drmModeAtomicReq *req, bool enable)
{
if (enable) {
const QSize mSize = modeSize();
const QSize bufferSize = m_primaryPlane->next() ? m_primaryPlane->next()->size() : pixelSize();
const QSize sourceSize = hardwareTransforms() ? bufferSize : mSize;
QRect targetRect = QRect(QPoint(0, 0), mSize);
if (mSize != sourceSize) {
targetRect.setSize(sourceSize.scaled(mSize, Qt::AspectRatioMode::KeepAspectRatio));
targetRect.setX((mSize.width() - targetRect.width()) / 2);
targetRect.setY((mSize.height() - targetRect.height()) / 2);
}
m_primaryPlane->setValue(DrmPlane::PropertyIndex::SrcX, 0);
m_primaryPlane->setValue(DrmPlane::PropertyIndex::SrcY, 0);
m_primaryPlane->setValue(DrmPlane::PropertyIndex::SrcW, sourceSize.width() << 16);
m_primaryPlane->setValue(DrmPlane::PropertyIndex::SrcH, sourceSize.height() << 16);
m_primaryPlane->setValue(DrmPlane::PropertyIndex::CrtcX, targetRect.x());
m_primaryPlane->setValue(DrmPlane::PropertyIndex::CrtcY, targetRect.y());
m_primaryPlane->setValue(DrmPlane::PropertyIndex::CrtcW, targetRect.width());
m_primaryPlane->setValue(DrmPlane::PropertyIndex::CrtcH, targetRect.height());
m_primaryPlane->setValue(DrmPlane::PropertyIndex::CrtcId, m_crtc->id());
} else {
m_primaryPlane->setCurrent(nullptr);
m_primaryPlane->setNext(nullptr);
m_primaryPlane->setValue(DrmPlane::PropertyIndex::SrcX, 0);
m_primaryPlane->setValue(DrmPlane::PropertyIndex::SrcY, 0);
m_primaryPlane->setValue(DrmPlane::PropertyIndex::SrcW, 0);
m_primaryPlane->setValue(DrmPlane::PropertyIndex::SrcH, 0);
m_primaryPlane->setValue(DrmPlane::PropertyIndex::CrtcX, 0);
m_primaryPlane->setValue(DrmPlane::PropertyIndex::CrtcY, 0);
m_primaryPlane->setValue(DrmPlane::PropertyIndex::CrtcW, 0);
m_primaryPlane->setValue(DrmPlane::PropertyIndex::CrtcH, 0);
m_primaryPlane->setValue(DrmPlane::PropertyIndex::CrtcId, 0);
}
m_conn->setValue(DrmConnector::PropertyIndex::CrtcId, enable ? m_crtc->id() : 0);
m_crtc->setValue(DrmCrtc::PropertyIndex::ModeId, enable ? m_blobId : 0);
m_crtc->setValue(DrmCrtc::PropertyIndex::Active, enable);
bool ret = true;
ret &= m_conn->atomicPopulate(req);
ret &= m_crtc->atomicPopulate(req);
return ret;
}
int DrmOutput::gammaRampSize() const
{
return m_crtc->gammaRampSize();
@ -851,7 +418,7 @@ int DrmOutput::gammaRampSize() const
bool DrmOutput::setGammaRamp(const GammaRamp &gamma)
{
return m_crtc->setGammaRamp(gamma);
return m_pipeline->setGammaRamp(gamma);
}
}

View file

@ -3,6 +3,7 @@
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2021 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
@ -32,6 +33,7 @@ class DrmConnector;
class DrmCrtc;
class Cursor;
class DrmGpu;
class DrmPipeline;
class KWIN_EXPORT DrmOutput : public AbstractWaylandOutput
{
@ -42,25 +44,21 @@ public:
RenderLoop *renderLoop() const override;
bool init();
///queues deleting the output after a page flip has completed.
void teardown();
void releaseBuffers();
bool showCursor(DrmDumbBuffer *buffer);
bool showCursor();
bool hideCursor();
bool updateCursor();
void moveCursor();
bool init(drmModeConnector *connector);
bool present(const QSharedPointer<DrmBuffer> &buffer);
void pageFlipped();
bool isDpmsEnabled() const {
// We care for current as well as pending mode in order to allow first present in AMS.
return m_dpmsModePending == DpmsMode::On;
}
bool updateCursor();
bool moveCursor();
bool showCursor();
bool hideCursor();
DpmsMode dpmsModePending() const {
return m_dpmsModePending;
bool isDpmsEnabled() const {
return m_dpmsEnabled;
}
const DrmCrtc *crtc() const {
@ -94,36 +92,12 @@ private:
// and save the connector ids in the DrmCrtc instance.
DrmOutput(DrmBackend *backend, DrmGpu* gpu);
bool presentAtomically(const QSharedPointer<DrmBuffer> &buffer);
void initOutputDevice();
enum class AtomicCommitMode {
Test,
Real
};
bool doAtomicCommit(AtomicCommitMode mode);
bool presentLegacy(const QSharedPointer<DrmBuffer> &buffer);
bool setModeLegacy(DrmBuffer *buffer);
void initOutputDevice(drmModeConnector *connector);
bool isCurrentMode(const drmModeModeInfo *mode) const;
void atomicEnable();
void atomicDisable();
void updateEnablement(bool enable) override;
bool dpmsAtomicOff();
bool dpmsLegacyApply();
void dpmsFinishOn();
void dpmsFinishOff();
bool atomicReqModesetPopulate(drmModeAtomicReq *req, bool enable);
void setDpmsMode(DpmsMode mode) override;
void updateMode(int modeIndex) override;
void updateMode(uint32_t width, uint32_t height, uint32_t refreshRate);
void setCurrentModeInternal();
void updateTransform(Transform transform) override;
int gammaRampSize() const override;
@ -131,31 +105,18 @@ private:
DrmBackend *m_backend;
DrmGpu *m_gpu;
DrmConnector *m_conn = nullptr;
DrmCrtc *m_crtc = nullptr;
bool m_lastGbm = false;
drmModeModeInfo m_mode;
DpmsMode m_dpmsModePending = DpmsMode::On;
RenderLoop *m_renderLoop;
uint32_t m_blobId = 0;
DrmPlane *m_primaryPlane = nullptr;
DrmPlane *m_cursorPlane = nullptr;
QVector<DrmPlane*> m_nextPlanesFlipList;
bool m_pageFlipPending = false;
bool m_atomicOffPending = false;
bool m_modesetRequested = true;
DrmConnector *m_conn = nullptr;
DrmCrtc *m_crtc = nullptr;
struct {
Transform transform;
drmModeModeInfo mode;
DrmPlane::Transformations planeTransformations;
QPoint globalPos;
bool valid = false;
} m_lastWorkingState;
QScopedPointer<DrmDumbBuffer> m_cursor[2];
int m_cursorIndex = 0;
bool m_hasNewCursor = false;
RenderLoop *m_renderLoop;
DrmPipeline *m_pipeline = nullptr;
bool m_dpmsEnabled = true;
QSharedPointer<DrmDumbBuffer> m_cursor;
bool m_firstCommit = true;
bool m_pageFlipPending = false;
bool m_deleted = false;
};

View file

@ -0,0 +1,337 @@
/*
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 "drm_pipeline.h"
#include "logging.h"
#include "drm_gpu.h"
#include "drm_object_connector.h"
#include "drm_object_crtc.h"
#include "drm_object_plane.h"
#include "drm_buffer.h"
#include "cursor.h"
#include "session.h"
#include "abstract_output.h"
#include <errno.h>
namespace KWin
{
DrmPipeline::DrmPipeline(void *pageflipUserData, DrmGpu *gpu, DrmConnector *conn, DrmCrtc *crtc, DrmPlane *primaryPlane, DrmPlane *cursorPlane)
: m_pageflipUserData(pageflipUserData), m_gpu(gpu), m_connector(conn), m_crtc(crtc), m_primaryPlane(primaryPlane)
{
m_cursor.plane = cursorPlane;
const auto &mode = m_connector->currentMode();
m_mode.mode = mode.mode;
m_mode.sourceSize = mode.size;
m_mode.enabled = true;
m_mode.blobId = -1;// = keep mode
}
DrmPipeline::~DrmPipeline()
{
if (m_mode.blobId > 0) {
drmModeDestroyPropertyBlob(m_gpu->fd(), m_mode.blobId);
}
if (m_gamma.blobId > 0) {
drmModeDestroyPropertyBlob(m_gpu->fd(), m_gamma.blobId);
}
}
bool DrmPipeline::present(const QSharedPointer<DrmBuffer> &buffer)
{
if (m_gpu->useEglStreams() && !m_mode.changed && !m_gamma.changed) {
// EglStreamBackend queues normal page flips through EGL,
// modesets etc are performed through DRM-KMS
return true;
}
setPrimaryBuffer(buffer);
bool result = m_gpu->atomicModeSetting() ? atomicCommit(false) : presentLegacy();
if (result) {
m_mode.changed = false;
m_gamma.changed = false;
} else {
qCWarning(KWIN_DRM) << "Present failed!" << strerror(errno);
}
return result;
}
bool DrmPipeline::test()
{
if (m_gpu->atomicModeSetting()) {
return atomicCommit(true);
} else {
return true;
}
}
bool DrmPipeline::atomicCommit(bool testOnly)
{
uint32_t flags = 0;
drmModeAtomicReq *req = drmModeAtomicAlloc();
if (!req) {
qCDebug(KWIN_DRM) << "Failed to allocate drmModeAtomicReq!" << strerror(errno);
return false;
}
if (testOnly) {
checkTestBuffer();
}
m_primaryPlane->setNext(testOnly ? m_testBuffer : m_primaryBuffer);
for (int i = 0; i < m_overlayPlanes.count(); i++) {
m_overlayPlanes[i]->setNext(testOnly ? m_testBuffer : m_overlayBuffers[i]);
}
bool result = populateAtomicValues(req, flags);
if (result && drmModeAtomicCommit(m_gpu->fd(), req, (flags & (~DRM_MODE_PAGE_FLIP_EVENT)) | DRM_MODE_ATOMIC_TEST_ONLY, m_pageflipUserData)) {
result = false;
}
if (!testOnly && result && drmModeAtomicCommit(m_gpu->fd(), req, flags, m_pageflipUserData)) {
qCCritical(KWIN_DRM) << "Atomic commit failed! This never should've happened!" << strerror(errno);
result = false;
}
drmModeAtomicFree(req);
return result;
}
bool DrmPipeline::populateAtomicValues(drmModeAtomicReq* req, uint32_t &flags)
{
if (!m_gpu->useEglStreams() && m_mode.enabled) {
flags |= DRM_MODE_PAGE_FLIP_EVENT;
}
if (m_mode.changed) {
flags |= DRM_MODE_ATOMIC_ALLOW_MODESET;
} else {
flags |= DRM_MODE_ATOMIC_NONBLOCK;
}
m_connector->setValue(DrmConnector::PropertyIndex::CrtcId, m_mode.enabled ? m_crtc->id() : 0);
if (!m_connector->atomicPopulate(req)) {
qCWarning(KWIN_DRM) << "Atomic populate for connector failed!";
return false;
}
m_crtc->setValue(DrmCrtc::PropertyIndex::ModeId, m_mode.enabled ? m_mode.blobId : 0);
m_crtc->setValue(DrmCrtc::PropertyIndex::Active, m_mode.enabled ? 1 : 0);
m_crtc->setValue(DrmCrtc::PropertyIndex::Gamma_LUT, m_mode.enabled ? m_gamma.blobId : 0);
if (!m_crtc->atomicPopulate(req)) {
qCWarning(KWIN_DRM) << "Atomic populate for crtc failed!";
return false;
}
QSize modesize = QSize(m_mode.mode.hdisplay, m_mode.mode.vdisplay);
m_primaryPlane->setScaled(m_primaryPlane->next() ? m_primaryPlane->next()->size() : modesize, modesize, m_crtc->id(), m_mode.enabled);
if (!m_primaryPlane->atomicPopulate(req)) {
qCWarning(KWIN_DRM) << "Atomic populate for primary plane failed!";
return false;
}
if (m_cursor.plane) {
const QSize &size = m_cursor.buffer ? m_cursor.buffer->size() : QSize(0, 0);
m_cursor.plane->setNext(m_cursor.buffer);
m_cursor.plane->set(size, m_cursor.pos, size, m_crtc->id(), m_mode.enabled && m_cursor.buffer != nullptr);
if (!m_cursor.plane->atomicPopulate(req)) {
qCWarning(KWIN_DRM) << "Atomic populate for cursor plane failed!";
return false;
}
}
for (const auto &overlay : qAsConst(m_overlayPlanes)) {
overlay->setScaled(overlay->next() ? overlay->next()->size() : modesize, modesize, m_crtc->id(), m_mode.enabled);
if (!overlay->atomicPopulate(req)) {
qCWarning(KWIN_DRM) << "Atomic populate for overlay plane failed!";
return false;
}
}
return true;
}
bool DrmPipeline::presentLegacy()
{
uint32_t flags = DRM_MODE_PAGE_FLIP_EVENT;
m_crtc->setNext(m_primaryBuffer);
if (drmModePageFlip(m_gpu->fd(), m_crtc->id(), m_primaryBuffer ? m_primaryBuffer->bufferId() : 0, flags, m_pageflipUserData)) {
qCWarning(KWIN_DRM) << "Page flip failed:" << strerror(errno) << m_primaryBuffer;
return false;
}
return true;
}
bool DrmPipeline::addOverlayPlane(DrmPlane *plane)
{
if (m_gpu->atomicModeSetting()) {
m_overlayPlanes << plane;
if (!atomicCommit(true)) {
qCWarning(KWIN_DRM) << "Could not add overlay plane!";
m_overlayPlanes.removeOne(plane);
return false;
}
m_overlayBuffers << nullptr;
} else {
return false;
}
return true;
}
void DrmPipeline::setPrimaryBuffer(const QSharedPointer<DrmBuffer> &buffer)
{
m_primaryBuffer = buffer;
}
bool DrmPipeline::modeset(QSize source, drmModeModeInfo mode)
{
auto oldMode = m_mode;
m_mode.sourceSize = source;
m_mode.mode = mode;
if (m_gpu->atomicModeSetting()) {
m_mode.changed = true;
if (drmModeCreatePropertyBlob(m_gpu->fd(), &mode, sizeof(drmModeModeInfo), &m_mode.blobId)) {
qCWarning(KWIN_DRM) << "Failed to create property blob for mode!" << strerror(errno);
m_mode = oldMode;
return false;
}
if (!atomicCommit(true)) {
drmModeDestroyPropertyBlob(m_gpu->fd(), m_mode.blobId);
m_mode = oldMode;
return false;
}
if (oldMode.blobId) {
drmModeDestroyPropertyBlob(m_gpu->fd(), oldMode.blobId);
}
} else {
checkTestBuffer();
uint32_t connId = m_connector->id();
if (drmModeSetCrtc(m_gpu->fd(), m_crtc->id(), m_testBuffer->bufferId(), 0, 0, &connId, 1, &mode)) {
m_mode = oldMode;
qCWarning(KWIN_DRM) << "Modeset failed:" << strerror(errno);
return false;
}
}
return true;
}
bool DrmPipeline::setCursor(const QSharedPointer<DrmDumbBuffer> &buffer)
{
auto oldCursor = m_cursor;
m_cursor.buffer = buffer;
if (m_gpu->atomicModeSetting() && m_cursor.plane) {
if (!atomicCommit(true)) {
qCWarning(KWIN_DRM) << "Could not set cursor!";
m_cursor = oldCursor;
return false;
}
} else {
const QSize &s = buffer ? buffer->size() : QSize(0, 0);
if (drmModeSetCursor(m_gpu->fd(), m_crtc->id(), m_cursor.buffer ? m_cursor.buffer->handle() : 0, s.width(), s.height())) {
m_cursor = oldCursor;
qCWarning(KWIN_DRM) << "Could not set cursor:" << strerror(errno);
return false;
}
}
return true;
}
bool DrmPipeline::moveCursor(QPoint pos)
{
auto cursor = m_cursor;
m_cursor.pos = pos;
if (m_gpu->atomicModeSetting() && m_cursor.plane) {
if (!atomicCommit(true)) {
m_cursor = cursor;
return false;
}
} else {
if (drmModeMoveCursor(m_gpu->fd(), m_crtc->id(), m_cursor.pos.x(), m_cursor.pos.y())) {
m_cursor = cursor;
return false;
}
}
return true;
}
bool DrmPipeline::setEnablement(bool enabled)
{
auto oldMode = m_mode;
m_mode.enabled = enabled;
setPrimaryBuffer(nullptr);
if (m_gpu->atomicModeSetting()) {
m_mode.changed = true;
// immediately commit if disabling as there will be no present
if (!atomicCommit(enabled)) {
m_mode = oldMode;
return false;
}
} else {
if (!m_connector->dpms()) {
qCWarning(KWIN_DRM) << "Setting DPMS failed: dpms property missing!";
m_mode = oldMode;
return false;
}
if (drmModeConnectorSetProperty(m_gpu->fd(), m_connector->id(), m_connector->dpms()->propId(), m_mode.enabled ? DRM_MODE_DPMS_ON : DRM_MODE_DPMS_OFF) < 0) {
qCWarning(KWIN_DRM) << "Setting DPMS failed";
m_mode = oldMode;
return false;
}
}
return true;
}
bool DrmPipeline::setGammaRamp(const GammaRamp &ramp)
{
// Apparently there are old Intel iGPUs that don't have full support
// for setting the gamma ramp with AMS -> fall back to legacy in that case
if (m_gpu->atomicModeSetting() && m_crtc->hasGammaProp()) {
auto oldGamma = m_gamma;
m_gamma.changed = true;
struct drm_color_lut *gamma = new drm_color_lut[ramp.size()];
for (uint32_t i = 0; i < ramp.size(); i++) {
gamma[i].red = ramp.red()[i];
gamma[i].green = ramp.green()[i];
gamma[i].blue = ramp.blue()[i];
}
bool result = !drmModeCreatePropertyBlob(m_gpu->fd(), gamma, ramp.size() * sizeof(struct drm_color_lut), &m_gamma.blobId);
delete[] gamma;
if (!result) {
qCWarning(KWIN_DRM) << "Could not create gamma LUT property blob" << strerror(errno);
m_gamma = oldGamma;
return false;
}
if (!atomicCommit(true)) {
qCWarning(KWIN_DRM) << "Setting gamma failed!" << strerror(errno);
drmModeDestroyPropertyBlob(m_gpu->fd(), m_gamma.blobId);
m_gamma = oldGamma;
return false;
}
if (oldGamma.blobId) {
drmModeDestroyPropertyBlob(m_gpu->fd(), oldGamma.blobId);
}
} else {
uint16_t *red = const_cast<uint16_t*>(ramp.red());
uint16_t *green = const_cast<uint16_t*>(ramp.green());
uint16_t *blue = const_cast<uint16_t*>(ramp.blue());
bool result = !drmModeCrtcSetGamma(m_gpu->fd(), m_crtc->id(), ramp.size(), red, green, blue);
if (!result) {
qCWarning(KWIN_DRM) << "setting gamma failed!" << strerror(errno);
return false;
}
}
return true;
}
void DrmPipeline::checkTestBuffer()
{
if (m_testBuffer && m_testBuffer->size() == m_mode.sourceSize) {
return;
}
m_testBuffer = m_gpu->createTestbuffer(m_mode.sourceSize);
}
}

View file

@ -0,0 +1,94 @@
/*
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 <QVector>
#include <QSharedPointer>
#include <xf86drmMode.h>
namespace KWin {
class DrmGpu;
class DrmConnector;
class DrmCrtc;
class DrmPlane;
class DrmBuffer;
class DrmDumbBuffer;
class GammaRamp;
class DrmPipeline
{
public:
DrmPipeline(void *pageflipUserData, DrmGpu *gpu, DrmConnector *conn, DrmCrtc *crtc, DrmPlane *primaryPlane, DrmPlane *cursorPlane);
~DrmPipeline();
/**
* tests the pending commit first and commits it if the test passes
* if the test fails, there is a guarantee for no lasting changes
*/
bool present(const QSharedPointer<DrmBuffer> &buffer);
/**
* tests the pending commit
* always returns true in legacy mode!
*/
bool test();
bool modeset(QSize source, drmModeModeInfo mode);
bool setCursor(const QSharedPointer<DrmDumbBuffer> &buffer);
bool setEnablement(bool enable);
bool setGammaRamp(const GammaRamp &ramp);
void setPrimaryBuffer(const QSharedPointer<DrmBuffer> &buffer);
bool moveCursor(QPoint pos);
bool addOverlayPlane(DrmPlane *plane);
private:
bool atomicCommit(bool testOnly);
bool populateAtomicValues(drmModeAtomicReq *req, uint32_t &flags);
bool presentLegacy();
void checkTestBuffer();
void *m_pageflipUserData = nullptr;
DrmGpu *m_gpu = nullptr;
DrmConnector *m_connector = nullptr;
DrmCrtc *m_crtc = nullptr;
DrmPlane *m_primaryPlane = nullptr;
QSharedPointer<DrmBuffer> m_primaryBuffer;
QSharedPointer<DrmBuffer> m_testBuffer;
QVector<DrmPlane*> m_overlayPlanes;
QVector<QSharedPointer<DrmBuffer>> m_overlayBuffers;
struct {
bool changed = false;
bool enabled = true;
QSize sourceSize = QSize(-1, -1);
drmModeModeInfo mode;
uint32_t blobId = 0;
} m_mode;
struct {
DrmPlane *plane = nullptr;
QPoint pos = QPoint(100, 100);
QSharedPointer<DrmDumbBuffer> buffer;
} m_cursor;
struct {
bool changed = false;
uint32_t blobId = 0;
} m_gamma;
};
}