diff --git a/plugins/platforms/drm/CMakeLists.txt b/plugins/platforms/drm/CMakeLists.txt --- a/plugins/platforms/drm/CMakeLists.txt +++ b/plugins/platforms/drm/CMakeLists.txt @@ -1,5 +1,9 @@ set(DRM_SOURCES drm_backend.cpp + drm_object.cpp + drm_object_connector.cpp + drm_object_crtc.cpp + drm_object_plane.cpp drm_output.cpp drm_buffer.cpp drm_inputeventfilter.cpp diff --git a/plugins/platforms/drm/drm_backend.h b/plugins/platforms/drm/drm_backend.h --- a/plugins/platforms/drm/drm_backend.h +++ b/plugins/platforms/drm/drm_backend.h @@ -53,6 +53,7 @@ class UdevMonitor; class DrmOutput; +class DrmPlane; class KWIN_EXPORT DrmBackend : public Platform @@ -84,15 +85,23 @@ QVector buffers() const { return m_buffers; } + QVector planes() const { + return m_planes; + } void bufferDestroyed(DrmBuffer *b); void outputWentOff(); void checkOutputsAreOn(); + // returns use of AMS, default is not/legacy + bool atomicModeSetting() const { + return m_atomicModeSetting; + } + + public Q_SLOTS: void turnOutputsOn(); - Q_SIGNALS: void outputRemoved(KWin::DrmOutput *output); void outputAdded(KWin::DrmOutput *output); @@ -122,11 +131,14 @@ int m_drmId = 0; QVector m_outputs; DrmBuffer *m_cursor[2]; + bool m_atomicModeSetting = false; bool m_cursorEnabled = false; int m_cursorIndex = 0; int m_pageFlipsPending = 0; bool m_active = false; QVector m_buffers; + // all available planes: primarys, cursors and overlays + QVector m_planes; QScopedPointer m_dpmsFilter; KWayland::Server::OutputManagementInterface *m_outputManagement = nullptr; }; diff --git a/plugins/platforms/drm/drm_backend.cpp b/plugins/platforms/drm/drm_backend.cpp --- a/plugins/platforms/drm/drm_backend.cpp +++ b/plugins/platforms/drm/drm_backend.cpp @@ -19,6 +19,9 @@ *********************************************************************/ #include "drm_backend.h" #include "drm_output.h" +#include "drm_object_connector.h" +#include "drm_object_crtc.h" +#include "drm_object_plane.h" #include "composite.h" #include "cursor.h" #include "logging.h" @@ -78,6 +81,7 @@ while (m_pageFlipsPending != 0) { QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents); } + qDeleteAll(m_planes); qDeleteAll(m_outputs); delete m_cursor[0]; delete m_cursor[1]; @@ -236,6 +240,46 @@ } ); m_drmId = device->sysNum(); + + // trying to activate Atomic Mode Setting (this means also Universal Planes) + if (qEnvironmentVariableIsSet("KWIN_DRM_AMS")) { + if (drmSetClientCap(m_fd, DRM_CLIENT_CAP_ATOMIC, 1) == 0) { + qCDebug(KWIN_DRM) << "Using Atomic Mode Setting."; + m_atomicModeSetting = true; + + ScopedDrmPointer planeResources(drmModeGetPlaneResources(m_fd)); + if (!planeResources) { + qCWarning(KWIN_DRM) << "Failed to get plane resources. Falling back to legacy mode"; + m_atomicModeSetting = false; + } + + if (m_atomicModeSetting) { + qCDebug(KWIN_DRM) << "Number of planes:" << planeResources->count_planes; + + // create the plane objects + for (unsigned int i = 0; i < planeResources->count_planes; ++i) { + drmModePlane *kplane = drmModeGetPlane(m_fd, planeResources->planes[i]); + DrmPlane *p = new DrmPlane(kplane->plane_id, m_fd); + + if (p->init()) { + p->setPossibleCrtcs(kplane->possible_crtcs); + p->setFormats(kplane->formats, kplane->count_formats); + m_planes << p; + } else { + delete p; + } + } + + if (m_planes.isEmpty()) { + qCWarning(KWIN_DRM) << "Failed to create any plane. Falling back to legacy mode"; + m_atomicModeSetting = false; + } + } + } else { + qCWarning(KWIN_DRM) << "drmSetClientCap for Atomic Mode Setting failed. Using legacy mode."; + } + } + queryResources(); if (m_outputs.isEmpty()) { qCWarning(KWIN_DRM) << "No outputs, cannot render, will terminate now"; @@ -314,12 +358,37 @@ DrmOutput *drmOutput = new DrmOutput(this); connect(drmOutput, &DrmOutput::dpmsChanged, this, &DrmBackend::outputDpmsChanged); drmOutput->m_crtcId = crtcId; + drmOutput->m_connector = connector->connector_id; + + if (m_atomicModeSetting) { + drmOutput->m_crtc = new DrmCrtc(crtcId, m_fd); + if (drmOutput->m_crtc->init()) { + drmOutput->m_crtc->setOutput(drmOutput); + } else { + qCWarning(KWIN_DRM) << "Crtc object failed, skipping output on connector" << connector->connector_id; + delete drmOutput->m_crtc; + delete drmOutput; + continue; + } + + drmOutput->m_conn = new DrmConnector(connector->connector_id, m_fd); + if (drmOutput->m_conn->init()) { + drmOutput->m_conn->setOutput(drmOutput); + } else { + qCWarning(KWIN_DRM) << "Connector object failed, skipping output on connector" << connector->connector_id; + delete drmOutput->m_conn; + delete drmOutput; + continue; + } + } + if (crtc->mode_valid) { drmOutput->m_mode = crtc->mode; } else { drmOutput->m_mode = connector->modes[0]; } - drmOutput->m_connector = connector->connector_id; + qCDebug(KWIN_DRM) << "For new output use mode " << drmOutput->m_mode.name; + if (!drmOutput->init(connector.data())) { qCWarning(KWIN_DRM) << "Failed to create output for connector " << connector->connector_id; delete drmOutput; @@ -619,6 +688,7 @@ { #if HAVE_GBM DrmBuffer *b = new DrmBuffer(this, surface); + b->m_deleteAfterPageFlip = true; m_buffers << b; return b; #else diff --git a/plugins/platforms/drm/drm_buffer.h b/plugins/platforms/drm/drm_buffer.h --- a/plugins/platforms/drm/drm_buffer.h +++ b/plugins/platforms/drm/drm_buffer.h @@ -58,6 +58,10 @@ bool isGbm() const { return m_bo != nullptr; } + bool deleteAfterPageFlip() const { + return m_deleteAfterPageFlip; + } + void releaseGbm(); private: @@ -74,6 +78,7 @@ quint64 m_bufferSize = 0; void *m_memory = nullptr; QImage *m_image = nullptr; + bool m_deleteAfterPageFlip = false; }; } diff --git a/plugins/platforms/drm/drm_object.h b/plugins/platforms/drm/drm_object.h new file mode 100644 --- /dev/null +++ b/plugins/platforms/drm/drm_object.h @@ -0,0 +1,137 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2016 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#ifndef KWIN_DRM_OBJECT_H +#define KWIN_DRM_OBJECT_H + +#include +#include + +// drm +#include + + +namespace KWin +{ + +class DrmOutput; + +class DrmObject +{ +public: + // creates drm object by its id delivered by the kernel + DrmObject(uint32_t object_id, int fd); + + virtual ~DrmObject() = 0; + + enum class AtomicReturn { + NoChange, + Success, + Error + }; + + virtual bool init() = 0; + + uint32_t id() const { + return m_id; + } + + DrmOutput* output() const { + return m_output; + } + void setOutput(DrmOutput* output) { + m_output = output; + } + + uint32_t propId(int index) { + return m_props[index]->propId(); + } + uint64_t propValue(int index) { + return m_props[index]->value(); + } + void setPropValue(int index, uint64_t new_value); + + uint32_t propsPending() { + return m_propsPending; + } + uint32_t propsValid() { + return m_propsValid; + } + void setPropsPending(uint32_t value) { + m_propsPending = value; + } + void setPropsValid(uint32_t value) { + m_propsValid = value; + } + + bool atomicAddProperty(drmModeAtomicReq *req, int prop, uint64_t value); + +protected: + const int m_fd = 0; + const uint32_t m_id = 0; + + DrmOutput *m_output = nullptr; + + QVector m_propsNames; // for comparision with received name of DRM object + class Property; + QVector m_props; + + uint32_t m_propsPending = 0; + uint32_t m_propsValid = 0; + + virtual bool initProps() = 0; // only derived classes know names and quantity of properties + void initProp(int n, drmModeObjectProperties *properties, QVector enumNames = QVector(0)); + + class Property + { + public: + Property(drmModePropertyRes *prop, uint64_t val, QVector enumNames); + virtual ~Property(); + + void initEnumMap(drmModePropertyRes *prop); + + uint64_t enumMap(int n) { + return m_enumMap[n]; // TODO: test on index out of bounds? + } + + uint32_t propId() { + return m_propId; + } + uint32_t value() { + return m_value; + } + void setValue(uint64_t new_value) { + m_value = new_value; + } + + private: + uint32_t m_propId = 0; + QByteArray m_propName; + + uint64_t m_value = 0; + QVector m_enumMap; + QVector m_enumNames; + }; +}; + + +} + +#endif + diff --git a/plugins/platforms/drm/drm_object.cpp b/plugins/platforms/drm/drm_object.cpp new file mode 100644 --- /dev/null +++ b/plugins/platforms/drm/drm_object.cpp @@ -0,0 +1,145 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2016 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#include "drm_object.h" +#include "logging.h" + +namespace KWin +{ + +/* + * Defintions for class DrmObject + */ + +DrmObject::DrmObject(uint32_t object_id, int fd) + : m_fd(fd) + , m_id(object_id) +{ +} + +DrmObject::~DrmObject() +{ + foreach(Property* p, m_props) + delete p; +} + +void DrmObject::initProp(int n, drmModeObjectProperties *properties, QVector enumNames) +{ + m_props.resize(m_propsNames.size()); + for (unsigned int i = 0; i < properties->count_props; ++i) { + drmModePropertyRes *prop = drmModeGetProperty(m_fd, properties->props[i]); + if (!prop) { + continue; + } + if (prop->name == m_propsNames[n]) { + qCDebug(KWIN_DRM).nospace() << m_id << ": " << prop->name << "' (id " << prop->prop_id + << "): " << properties->prop_values[i]; + m_props[n] = new Property(prop, properties->prop_values[i], enumNames); + } + drmModeFreeProperty(prop); + } +} + +void DrmObject::setPropValue(int index, uint64_t new_value) +{ + Q_ASSERT(index < m_props.size()); + m_props[index]->setValue(new_value); + return; +} + +bool DrmObject::atomicAddProperty(drmModeAtomicReq *req, int prop, uint64_t value) +{ + uint32_t mask = 1U << prop; + if ((m_propsPending | m_propsValid) & mask && value == propValue(prop)) { + // no change necessary, don't add property for next atomic commit + return true; + } + if (drmModeAtomicAddProperty(req, m_id, m_props[prop]->propId(), value) < 0) { + // error when adding property + return false; + } + m_propsPending |= mask; + m_propsValid &= ~mask; + // adding property was successful + return true; +} + +/* + * Defintions for struct Prop + */ + +DrmObject::Property::Property(drmModePropertyRes *prop, uint64_t val, QVector enumNames) + : m_propId(prop->prop_id) + , m_propName(prop->name) + , m_value(val) +{ + if (!enumNames.isEmpty()) { + qCDebug(KWIN_DRM) << m_propName << " has enums:" << enumNames; + m_enumNames = enumNames; + initEnumMap(prop); + } +} + +DrmObject::Property::~Property() = default; + +void DrmObject::Property::initEnumMap(drmModePropertyRes *prop) +{ + if (!(prop->flags & DRM_MODE_PROP_ENUM) || prop->count_enums < 1) { + qCWarning(KWIN_DRM) << "Property '" << prop->name << "' ( id =" + << m_propId << ") should be enum valued, but it is not."; + return; + } + + int nameCount = m_enumNames.size(); + m_enumMap.resize(nameCount); + + qCDebug(KWIN_DRM).nospace() << "Test all " << prop->count_enums << + " possible enums" <<":"; + + for (int i = 0; i < prop->count_enums; i++) { + + struct drm_mode_property_enum *en = &prop->enums[i]; + int j = 0; + + while (QByteArray(en->name) != m_enumNames[j]) { + j++; + if (j == nameCount) { + break; + } + } + + if (j == nameCount) { + qCWarning(KWIN_DRM).nospace() << m_propName << " has unrecognized enum '" << en->name << "'"; + } else { + qCDebug(KWIN_DRM).nospace() << "Enum '" + << en->name << "': runtime-value = " << en->value; + m_enumMap[j] = en->value; + } + } + + if (KWIN_DRM().isDebugEnabled()) { + for (int i = 0; i < m_enumMap.size(); i++) { + if (m_value == m_enumMap[i]) { + qCDebug(KWIN_DRM) << "=>" << m_propName << "with mapped enum value" << m_enumNames[i]; + } + } + } +} + +} diff --git a/plugins/platforms/drm/drm_object_connector.h b/plugins/platforms/drm/drm_object_connector.h new file mode 100644 --- /dev/null +++ b/plugins/platforms/drm/drm_object_connector.h @@ -0,0 +1,48 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2016 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#ifndef KWIN_DRM_OBJECT_CONNECTOR_H +#define KWIN_DRM_OBJECT_CONNECTOR_H + +#include "drm_object.h" + +namespace KWin +{ + +class DrmConnector : public DrmObject +{ +public: + DrmConnector(uint32_t connector_id, int fd); + + virtual ~DrmConnector(); + + bool init(); + + enum class PropertyIndex { + CrtcId = 0, + Count + }; + + bool initProps(); +}; + +} + +#endif + diff --git a/plugins/platforms/drm/drm_object_connector.cpp b/plugins/platforms/drm/drm_object_connector.cpp new file mode 100644 --- /dev/null +++ b/plugins/platforms/drm/drm_object_connector.cpp @@ -0,0 +1,64 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2016 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#include "drm_object_connector.h" +#include "logging.h" + +namespace KWin +{ + +DrmConnector::DrmConnector(uint32_t connector_id, int fd) + : DrmObject(connector_id, fd) +{ + +} + +DrmConnector::~DrmConnector() = default; + +bool DrmConnector::init() +{ + qCDebug(KWIN_DRM) << "Creating connector" << m_id; + + if (!initProps()) { + return false; + } + return true; +} + +bool DrmConnector::initProps() +{ + m_propsNames = { + QByteArrayLiteral("CRTC_ID"), + }; + + drmModeObjectProperties *properties = drmModeObjectGetProperties(m_fd, m_id, DRM_MODE_OBJECT_CONNECTOR); + if (!properties) { + qCWarning(KWIN_DRM) << "Failed to get properties for connector " << m_id ; + return false; + } + + int propCount = int(PropertyIndex::Count); + for (int j = 0; j < propCount; ++j) { + initProp(j, properties); + } + drmModeFreeObjectProperties(properties); + return true; +} + +} diff --git a/plugins/platforms/drm/drm_object_crtc.h b/plugins/platforms/drm/drm_object_crtc.h new file mode 100644 --- /dev/null +++ b/plugins/platforms/drm/drm_object_crtc.h @@ -0,0 +1,51 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2016 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#ifndef KWIN_DRM_OBJECT_CRTC_H +#define KWIN_DRM_OBJECT_CRTC_H + +#include "drm_object.h" + +namespace KWin +{ + +class DrmBuffer; + +class DrmCrtc : public DrmObject +{ +public: + DrmCrtc(uint32_t crtc_id, int fd); + + virtual ~DrmCrtc(); + + bool init(); + + enum class PropertyIndex { + ModeId = 0, + Active, + Count + }; + + bool initProps(); +}; + +} + +#endif + diff --git a/plugins/platforms/drm/drm_object_crtc.cpp b/plugins/platforms/drm/drm_object_crtc.cpp new file mode 100644 --- /dev/null +++ b/plugins/platforms/drm/drm_object_crtc.cpp @@ -0,0 +1,64 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2016 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#include "drm_object_crtc.h" +#include "logging.h" + +namespace KWin +{ + +DrmCrtc::DrmCrtc(uint32_t crtc_id, int fd) + : DrmObject(crtc_id, fd) +{ +} + +DrmCrtc::~DrmCrtc() = default; + +bool DrmCrtc::init() +{ + qCDebug(KWIN_DRM) << "Creating CRTC" << m_id; + + if (!initProps()) { + return false; + } + return true; +} + +bool DrmCrtc::initProps() +{ + m_propsNames = { + QByteArrayLiteral("MODE_ID"), + QByteArrayLiteral("ACTIVE"), + }; + + drmModeObjectProperties *properties = drmModeObjectGetProperties(m_fd, m_id, DRM_MODE_OBJECT_CRTC); + if (!properties) { + qCWarning(KWIN_DRM) << "Failed to get properties for crtc " << m_id ; + return false; + } + + int propCount = int(PropertyIndex::Count); + for (int j = 0; j < propCount; ++j) { + initProp(j, properties); + } + drmModeFreeObjectProperties(properties); + return true; +} + +} diff --git a/plugins/platforms/drm/drm_object_plane.h b/plugins/platforms/drm/drm_object_plane.h new file mode 100644 --- /dev/null +++ b/plugins/platforms/drm/drm_object_plane.h @@ -0,0 +1,106 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2016 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#ifndef KWIN_DRM_OBJECT_PLANE_H +#define KWIN_DRM_OBJECT_PLANE_H + +#include "drm_object.h" +// drm +#include + +namespace KWin +{ + +class DrmBuffer; + +class DrmPlane : public DrmObject +{ +public: + DrmPlane(uint32_t plane_id, int fd); + + virtual ~DrmPlane(); + + enum class PropertyIndex { + Type = 0, + SrcX, + SrcY, + SrcW, + SrcH, + CrtcX, + CrtcY, + CrtcW, + CrtcH, + FbId, + CrtcId, + Count + }; + + enum class TypeIndex { + Primary = 0, + Cursor, + Overlay, + Count + }; + + bool init(); + bool initProps(); + TypeIndex type(); + bool isCrtcSupported(uint32_t crtc); + DrmObject::AtomicReturn atomicReqPlanePopulate(drmModeAtomicReq *req); + + DrmBuffer *current(){ + return m_current; + } + DrmBuffer *next(){ + return m_next; + } + void setCurrent(DrmBuffer *b){ + m_current = b; + } + void setNext(DrmBuffer *b){ + m_next = b; + } + + QVector formats(){ + return m_formats; + } + void setFormats(uint32_t const *f, int fcount); + + void setPossibleCrtcs(uint32_t value){ + m_possibleCrtcs = value; + } + uint32_t possibleCrtcs(){ + return m_possibleCrtcs; + } + +private: + DrmBuffer *m_current = nullptr; + DrmBuffer *m_next = nullptr; + + // TODO: See weston drm_output_check_plane_format for future use of these member variables + QVector m_formats; // Possible formats, which can be presented on this plane + + // TODO: when using overlay planes in the future: restrict possible screens / crtcs of planes + uint32_t m_possibleCrtcs; +}; + +} + +#endif + diff --git a/plugins/platforms/drm/drm_object_plane.cpp b/plugins/platforms/drm/drm_object_plane.cpp new file mode 100644 --- /dev/null +++ b/plugins/platforms/drm/drm_object_plane.cpp @@ -0,0 +1,174 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2016 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#include "drm_object_plane.h" +#include "drm_buffer.h" +#include "drm_pointer.h" +#include "logging.h" + +namespace KWin +{ + +DrmPlane::DrmPlane(uint32_t plane_id, int fd) + : DrmObject(plane_id, fd) +{ +} + +DrmPlane::~DrmPlane() = default; + +bool DrmPlane::init() +{ + qCDebug(KWIN_DRM) << "Initialize plane" << m_id; + ScopedDrmPointer<_drmModePlane, &drmModeFreePlane> p(drmModeGetPlane(m_fd, m_id)); + + if (!p) { + qCWarning(KWIN_DRM) << "Failed to get kernel plane" << m_id; + return false; + } + + m_possibleCrtcs = p->possible_crtcs; + + m_formats.resize(p->count_formats); + for (int i = 0; i < p->count_formats; i++) { + m_formats[i] = p->formats[i]; + } + + if (!initProps()) { + return false; + } + return true; +} + +bool DrmPlane::initProps() +{ + m_propsNames = { + QByteArrayLiteral("type"), + QByteArrayLiteral("SRC_X"), + QByteArrayLiteral("SRC_Y"), + QByteArrayLiteral("SRC_W"), + QByteArrayLiteral("SRC_H"), + QByteArrayLiteral("CRTC_X"), + QByteArrayLiteral("CRTC_Y"), + QByteArrayLiteral("CRTC_W"), + QByteArrayLiteral("CRTC_H"), + QByteArrayLiteral("FB_ID"), + QByteArrayLiteral("CRTC_ID"), + }; + + QVector typeNames = { + QByteArrayLiteral("Primary"), + QByteArrayLiteral("Cursor"), + QByteArrayLiteral("Overlay"), + }; + + drmModeObjectProperties *properties = drmModeObjectGetProperties(m_fd, m_id, DRM_MODE_OBJECT_PLANE); + if (!properties){ + qCWarning(KWIN_DRM) << "Failed to get properties for plane " << m_id ; + return false; + } + + int propCount = int(PropertyIndex::Count); + for (int j = 0; j < propCount; ++j) { + if (j == int(PropertyIndex::Type)) { + initProp(j, properties, typeNames); + } else { + initProp(j, properties); + } + } + + drmModeFreeObjectProperties(properties); + return true; +} + +DrmPlane::TypeIndex DrmPlane::type() +{ + uint64_t value = propValue(int(PropertyIndex::Type)); + int typeCount = int(TypeIndex::Count); + for (int i = 0; i < typeCount; i++) { + if (m_props[int(PropertyIndex::Type)]->enumMap(i) == value) { + return TypeIndex(i); + } + } + return TypeIndex::Overlay; +} + +bool DrmPlane::isCrtcSupported(uint32_t crtc) +{ + ScopedDrmPointer<_drmModeRes, &drmModeFreeResources> res(drmModeGetResources(m_fd)); + if (!res) { + qCWarning(KWIN_DRM) << "Failed to get drm resources"; + } + for (int c = 0; c < res->count_crtcs; c++) { + if (res->crtcs[c] != crtc) { + continue; + } + qCDebug(KWIN_DRM) << "Mask " << m_possibleCrtcs << ", idx " << c; + if (m_possibleCrtcs & (1 << c)) { + return true; + } + } + qCDebug(KWIN_DRM) << "CRTC" << crtc << "not supported"; + return false; +} + + +void DrmPlane::setFormats(uint32_t const *f, int fcount) +{ + m_formats.resize(fcount); + for (int i = 0; i < fcount; i++) { + m_formats[i] = *f; + } +} + +DrmObject::AtomicReturn DrmPlane::atomicReqPlanePopulate(drmModeAtomicReq *req) +{ + bool ret = true; + + if (m_next) { + setPropValue(int(PropertyIndex::FbId), m_next->bufferId()); + } else { + setPropValue(int(PropertyIndex::FbId), 0); + setPropValue(int(PropertyIndex::SrcX), 0); + setPropValue(int(PropertyIndex::SrcY), 0); + setPropValue(int(PropertyIndex::SrcW), 0); + setPropValue(int(PropertyIndex::SrcH), 0); + setPropValue(int(PropertyIndex::CrtcX), 0); + setPropValue(int(PropertyIndex::CrtcY), 0); + setPropValue(int(PropertyIndex::CrtcW), 0); + setPropValue(int(PropertyIndex::CrtcH), 0); + } + + m_propsPending = 0; + + for (int i = int(PropertyIndex::SrcX); i < int(PropertyIndex::CrtcId); i++) { + ret &= atomicAddProperty(req, i, propValue(i)); + } + ret &= atomicAddProperty(req, int(PropertyIndex::CrtcId), m_next ? propValue(int(PropertyIndex::CrtcId)) : 0); + + if (!ret) { + qCWarning(KWIN_DRM) << "Failed to populate atomic plane" << m_id; + return DrmObject::AtomicReturn::Error; + } + if (!m_propsPending) { + return DrmObject::AtomicReturn::NoChange; + } + return DrmObject::AtomicReturn::Success; +} + +} diff --git a/plugins/platforms/drm/drm_output.h b/plugins/platforms/drm/drm_output.h --- a/plugins/platforms/drm/drm_output.h +++ b/plugins/platforms/drm/drm_output.h @@ -21,11 +21,13 @@ #define KWIN_DRM_OUTPUT_H #include "drm_pointer.h" +#include "drm_object.h" #include #include #include #include +#include #include namespace KWayland @@ -44,6 +46,9 @@ class DrmBackend; class DrmBuffer; +class DrmPlane; +class DrmConnector; +class DrmCrtc; class DrmOutput : public QObject { @@ -59,9 +64,9 @@ void showCursor(DrmBuffer *buffer); void hideCursor(); void moveCursor(const QPoint &globalPos); + bool init(drmModeConnector *connector); bool present(DrmBuffer *buffer); void pageFlipped(); - bool init(drmModeConnector *connector); void restoreSaved(); bool blank(); @@ -75,6 +80,7 @@ QRect geometry() const; QString name() const; int currentRefreshRate() const; + // These values are defined by the kernel enum class DpmsMode { On = DRM_MODE_DPMS_ON, Standby = DRM_MODE_DPMS_STANDBY, @@ -97,13 +103,20 @@ friend class DrmBackend; DrmOutput(DrmBackend *backend); void cleanupBlackBuffer(); - bool setMode(DrmBuffer *buffer); + bool presentAtomically(DrmBuffer *buffer); + bool presentLegacy(DrmBuffer *buffer); + bool setModeLegacy(DrmBuffer *buffer); void initEdid(drmModeConnector *connector); void initDpms(drmModeConnector *connector); bool isCurrentMode(const drmModeModeInfo *mode) const; void initUuid(); void setGlobalPos(const QPoint &pos); + void pageFlippedBufferRemover(DrmBuffer *oldbuffer, DrmBuffer *newbuffer); + bool initPrimaryPlane(); + bool initCursorPlane(); + DrmObject::AtomicReturn atomicReqModesetPopulate(drmModeAtomicReq *req, bool enable); + DrmBackend *m_backend; QPoint m_globalPos; quint32 m_crtcId = 0; @@ -112,10 +125,11 @@ bool m_lastGbm = false; drmModeModeInfo m_mode; DrmBuffer *m_currentBuffer = nullptr; + DrmBuffer *m_nextBuffer = nullptr; DrmBuffer *m_blackBuffer = nullptr; struct CrtcCleanup { static void inline cleanup(_drmModeCrtc *ptr) { - drmModeFreeCrtc(ptr); + drmModeFreeCrtc(ptr); // TODO: Atomically? See compositor-drm.c l.3670 } }; Edid m_edid; @@ -126,8 +140,14 @@ KWin::ScopedDrmPointer<_drmModeProperty, &drmModeFreeProperty> m_dpms; DpmsMode m_dpmsMode = DpmsMode::On; QByteArray m_uuid; -}; + DrmConnector *m_conn = nullptr; + DrmCrtc *m_crtc = nullptr; + uint32_t m_blobId = 0; + DrmPlane* m_primaryPlane = nullptr; + DrmPlane* m_cursorPlane = nullptr; + QVector m_planesFlipList; +}; } diff --git a/plugins/platforms/drm/drm_output.cpp b/plugins/platforms/drm/drm_output.cpp --- a/plugins/platforms/drm/drm_output.cpp +++ b/plugins/platforms/drm/drm_output.cpp @@ -19,6 +19,11 @@ *********************************************************************/ #include "drm_output.h" #include "drm_backend.h" +#include "drm_object_plane.h" +#include "drm_object_crtc.h" +#include "drm_object_connector.h" + +#include #include "composite.h" #include "logind.h" @@ -58,6 +63,8 @@ { hideCursor(); cleanupBlackBuffer(); + delete m_crtc; + delete m_conn; delete m_waylandOutput.data(); delete m_waylandOutputDevice.data(); } @@ -97,55 +104,6 @@ return QRect(m_globalPos, size()); } -bool DrmOutput::present(DrmBuffer *buffer) -{ - if (!buffer || buffer->bufferId() == 0) { - return false; - } - if (!LogindIntegration::self()->isActiveSession()) { - m_currentBuffer = buffer; - return false; - } - if (m_dpmsMode != DpmsMode::On) { - return false; - } - if (m_currentBuffer) { - return false; - } - if (m_lastStride != buffer->stride() || m_lastGbm != buffer->isGbm()) { - // need to set a new mode first - if (!setMode(buffer)) { - return false; - } - } - const bool ok = drmModePageFlip(m_backend->fd(), m_crtcId, buffer->bufferId(), DRM_MODE_PAGE_FLIP_EVENT, this) == 0; - if (ok) { - m_currentBuffer = buffer; - } else { - qCWarning(KWIN_DRM) << "Page flip failed"; - buffer->releaseGbm(); - } - return ok; -} - -void DrmOutput::pageFlipped() -{ - if (!m_currentBuffer) { - return; - } - m_currentBuffer->releaseGbm(); - m_currentBuffer = nullptr; - cleanupBlackBuffer(); -} - -void DrmOutput::cleanupBlackBuffer() -{ - if (m_blackBuffer) { - delete m_blackBuffer; - m_blackBuffer = nullptr; - } -} - static KWayland::Server::OutputInterface::DpmsMode toWaylandDpmsMode(DrmOutput::DpmsMode mode) { using namespace KWayland::Server; @@ -185,6 +143,11 @@ initEdid(connector); initDpms(connector); initUuid(); + if (m_backend->atomicModeSetting()) { + if (!initPrimaryPlane()) { + return false; + } + } m_savedCrtc.reset(drmModeGetCrtc(m_backend->fd(), m_crtcId)); if (!blank()) { return false; @@ -240,6 +203,10 @@ // read in mode information 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]; KWayland::Server::OutputInterface::ModeFlags flags; KWayland::Server::OutputDeviceInterface::ModeFlags deviceflags; @@ -321,31 +288,6 @@ && qstrcmp(mode->name, m_mode.name) == 0; } -bool DrmOutput::blank() -{ - if (!m_blackBuffer) { - m_blackBuffer = m_backend->createBuffer(size()); - if (!m_blackBuffer->map()) { - cleanupBlackBuffer(); - return false; - } - m_blackBuffer->image()->fill(Qt::black); - } - return setMode(m_blackBuffer); -} - -bool DrmOutput::setMode(DrmBuffer *buffer) -{ - if (drmModeSetCrtc(m_backend->fd(), m_crtcId, buffer->bufferId(), 0, 0, &m_connector, 1, &m_mode) == 0) { - m_lastStride = buffer->stride(); - m_lastGbm = buffer->isGbm(); - return true; - } else { - qCWarning(KWIN_DRM) << "Mode setting failed"; - return false; - } -} - static bool verifyEdidHeader(drmModePropertyBlobPtr edid) { const uint8_t *data = reinterpret_cast(edid->data); @@ -505,6 +447,61 @@ m_edid.physicalSize = extractPhysicalSize(edid.data()); } +bool DrmOutput::initPrimaryPlane() +{ + for (int i = 0; i < m_backend->planes().size(); ++i) { + DrmPlane* p = m_backend->planes()[i]; + if (!p) { + continue; + } + if (p->type() != DrmPlane::TypeIndex::Primary) { + continue; + } + if (p->output()) { // Plane already has an output + continue; + } + if (m_primaryPlane) { // Output already has a primary plane + continue; + } + if (!p->isCrtcSupported(m_crtcId)) { + continue; + } + p->setOutput(this); + m_primaryPlane = p; + qCDebug(KWIN_DRM) << "Initialized primary plane" << p->id() << "on CRTC" << m_crtcId; + return true; + } + qCCritical(KWIN_DRM) << "Failed to initialize primary plane."; + return false; +} + +bool DrmOutput::initCursorPlane() // TODO: Add call in init (but needs layer support in general first) +{ + for (int i = 0; i < m_backend->planes().size(); ++i) { + DrmPlane* p = m_backend->planes()[i]; + if (!p) { + continue; + } + if (p->type() != DrmPlane::TypeIndex::Cursor) { + continue; + } + if (p->output()) { // Plane already has an output + continue; + } + if (m_cursorPlane) { // Output already has a cursor plane + continue; + } + if (!p->isCrtcSupported(m_crtcId)) { + continue; + } + p->setOutput(this); + m_cursorPlane = p; + qCDebug(KWIN_DRM) << "Initialized cursor plane" << p->id() << "on CRTC" << m_crtcId; + return true; + } + return false; +} + void DrmOutput::initDpms(drmModeConnector *connector) { for (int i = 0; i < connector->count_props; ++i) { @@ -525,12 +522,30 @@ return; } if (mode == m_dpmsMode) { + qCDebug(KWIN_DRM) << "New DPMS mode equals old mode. DPMS unchanged."; return; } - if (drmModeConnectorSetProperty(m_backend->fd(), m_connector, m_dpms->prop_id, uint64_t(mode)) != 0) { - qCWarning(KWIN_DRM) << "Setting DPMS failed"; - return; + + if (m_backend->atomicModeSetting()) { + drmModeAtomicReq *req = drmModeAtomicAlloc(); + + if (atomicReqModesetPopulate(req, mode == DpmsMode::On) == DrmObject::AtomicReturn::Error) { + qCWarning(KWIN_DRM) << "Failed to populate atomic request for output" << m_crtcId; + return; + } + if (drmModeAtomicCommit(m_backend->fd(), req, DRM_MODE_ATOMIC_ALLOW_MODESET, this)) { + qCWarning(KWIN_DRM) << "Failed to commit atomic request for output" << m_crtcId; + } else { + qCDebug(KWIN_DRM) << "DPMS set for output" << m_crtcId; + } + drmModeAtomicFree(req); + } else { + if (drmModeConnectorSetProperty(m_backend->fd(), m_connector, m_dpms->prop_id, uint64_t(mode)) < 0) { + qCWarning(KWIN_DRM) << "Setting DPMS failed"; + return; + } } + m_dpmsMode = mode; if (m_waylandOutput) { m_waylandOutput->setDpmsMode(toWaylandDpmsMode(m_dpmsMode)); @@ -621,5 +636,251 @@ return true; } +void DrmOutput::pageFlipped() +{ + if (m_backend->atomicModeSetting()){ + foreach (DrmPlane *p, m_planesFlipList) { + pageFlippedBufferRemover(p->current(), p->next()); + p->setCurrent(p->next()); + p->setNext(nullptr); + } + m_planesFlipList.clear(); + + } else { + if (!m_nextBuffer) { + return; + } + pageFlippedBufferRemover(m_currentBuffer, m_nextBuffer); + m_currentBuffer = m_nextBuffer; + m_nextBuffer = nullptr; + } + cleanupBlackBuffer(); +} + +void DrmOutput::pageFlippedBufferRemover(DrmBuffer *oldbuffer, DrmBuffer *newbuffer) +{ + if (newbuffer->deleteAfterPageFlip()) { + if ( oldbuffer && oldbuffer != newbuffer ) { + delete oldbuffer; + } + } else { + // although oldbuffer's pointer is remapped in pageFlipped(), + // we ignore the pointer completely anywhere else in this case + newbuffer->releaseGbm(); + } +} + +void DrmOutput::cleanupBlackBuffer() +{ + if (m_blackBuffer) { + delete m_blackBuffer; + m_blackBuffer = nullptr; + } +} + +bool DrmOutput::blank() +{ + if (!m_blackBuffer) { + m_blackBuffer = m_backend->createBuffer(size()); + if (!m_blackBuffer->map()) { + cleanupBlackBuffer(); + return false; + } + m_blackBuffer->image()->fill(Qt::black); + } + // TODO: Do this atomically + return setModeLegacy(m_blackBuffer); +} + +bool DrmOutput::present(DrmBuffer *buffer) +{ + if (!buffer || buffer->bufferId() == 0) { + return false; + } + if (m_backend->atomicModeSetting()) { + return presentAtomically(buffer); + } else { + return presentLegacy(buffer); + } +} + +bool DrmOutput::presentAtomically(DrmBuffer *buffer) +{ + if (!LogindIntegration::self()->isActiveSession()) { + qCWarning(KWIN_DRM) << "Logind session not active."; + return false; + } + if (m_dpmsMode != DpmsMode::On) { + qCWarning(KWIN_DRM) << "No present() while screen off."; + return false; + } + if (m_primaryPlane->next()) { + qCWarning(KWIN_DRM) << "Page not yet flipped."; + return false; + } + + DrmObject::AtomicReturn ret; + uint32_t flags = DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT; + + // TODO: throwing an exception would be really handy here! (would mean change of compile options) + drmModeAtomicReq *req = drmModeAtomicAlloc(); + if (!req) { + qCWarning(KWIN_DRM) << "DRM: couldn't allocate atomic request"; + delete buffer; + return false; + } + + // Do we need to set a new mode first? + bool doModeset = !m_primaryPlane->current(); + if (doModeset) { + qCDebug(KWIN_DRM) << "Atomic Modeset requested"; + + if (drmModeCreatePropertyBlob(m_backend->fd(), &m_mode, sizeof(m_mode), &m_blobId)) { + qCWarning(KWIN_DRM) << "Failed to create property blob"; + delete buffer; + return false; + } + + ret = atomicReqModesetPopulate(req, true); + if (ret == DrmObject::AtomicReturn::Error){ + drmModeAtomicFree(req); + delete buffer; + return false; + } + if (ret == DrmObject::AtomicReturn::Success) { + flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; + } + } + + m_primaryPlane->setNext(buffer); // TODO: Later not only use the primary plane for the buffer! + // i.e.: Assign planes + bool anyDamage = false; + foreach (DrmPlane* p, m_backend->planes()){ + if (p->output() != this) { + continue; + } + ret = p->atomicReqPlanePopulate(req); + if (ret == DrmObject::AtomicReturn::Error) { + drmModeAtomicFree(req); + m_primaryPlane->setNext(nullptr); + m_planesFlipList.clear(); + delete buffer; + return false; + } + if (ret == DrmObject::AtomicReturn::Success) { + anyDamage = true; + m_planesFlipList << p; + } + } + + // no damage but force flip for atleast the primary plane anyway + if (!anyDamage) { + m_primaryPlane->setPropsValid(0); + if (m_primaryPlane->atomicReqPlanePopulate(req) == DrmObject::AtomicReturn::Error) { + drmModeAtomicFree(req); + m_primaryPlane->setNext(nullptr); + m_planesFlipList.clear(); + delete buffer; + return false; + } + m_planesFlipList << m_primaryPlane; + } + + if (drmModeAtomicCommit(m_backend->fd(), req, flags, this)) { + qCWarning(KWIN_DRM) << "Atomic request failed to commit:" << strerror(errno); + drmModeAtomicFree(req); + m_primaryPlane->setNext(nullptr); + m_planesFlipList.clear(); + delete buffer; + return false; + } + + if (doModeset) { + m_crtc->setPropsValid(m_crtc->propsValid() | m_crtc->propsPending()); + m_conn->setPropsValid(m_conn->propsValid() | m_conn->propsPending()); + } + foreach (DrmPlane* p, m_planesFlipList) { + p->setPropsValid(p->propsValid() | p->propsPending()); + } + + drmModeAtomicFree(req); + return true; +} + + +bool DrmOutput::presentLegacy(DrmBuffer *buffer) +{ + if (m_nextBuffer) { + return false; + } + if (!LogindIntegration::self()->isActiveSession()) { + m_nextBuffer = buffer; + return false; + } + if (m_dpmsMode != DpmsMode::On) { + return false; + } + + // Do we need to set a new mode first? + if (m_lastStride != buffer->stride() || m_lastGbm != buffer->isGbm()){ + if (!setModeLegacy(buffer)) + return false; + } + int errno_save = 0; + const bool ok = drmModePageFlip(m_backend->fd(), m_crtcId, buffer->bufferId(), DRM_MODE_PAGE_FLIP_EVENT, this) == 0; + if (ok) { + m_nextBuffer = buffer; + } else { + errno_save = errno; + qCWarning(KWIN_DRM) << "Page flip failed:" << strerror(errno); + delete buffer; + } + return ok; +} + +bool DrmOutput::setModeLegacy(DrmBuffer *buffer) +{ + if (drmModeSetCrtc(m_backend->fd(), m_crtcId, buffer->bufferId(), 0, 0, &m_connector, 1, &m_mode) == 0) { + m_lastStride = buffer->stride(); + m_lastGbm = buffer->isGbm(); + return true; + } else { + qCWarning(KWIN_DRM) << "Mode setting failed"; + return false; + } +} + +DrmObject::AtomicReturn DrmOutput::atomicReqModesetPopulate(drmModeAtomicReq *req, bool enable) +{ + if (enable) { + m_primaryPlane->setPropValue(int(DrmPlane::PropertyIndex::SrcW), m_mode.hdisplay << 16); + m_primaryPlane->setPropValue(int(DrmPlane::PropertyIndex::SrcH), m_mode.vdisplay << 16); + m_primaryPlane->setPropValue(int(DrmPlane::PropertyIndex::CrtcW), m_mode.hdisplay); + m_primaryPlane->setPropValue(int(DrmPlane::PropertyIndex::CrtcH), m_mode.vdisplay); + } else { + m_primaryPlane->setPropValue(int(DrmPlane::PropertyIndex::SrcW), 0); + m_primaryPlane->setPropValue(int(DrmPlane::PropertyIndex::SrcH), 0); + m_primaryPlane->setPropValue(int(DrmPlane::PropertyIndex::CrtcW), 0); + m_primaryPlane->setPropValue(int(DrmPlane::PropertyIndex::CrtcH), 0); + } + + bool ret = true; + + m_crtc->setPropsPending(0); + m_conn->setPropsPending(0); + + ret &= m_conn->atomicAddProperty(req, int(DrmConnector::PropertyIndex::CrtcId), enable ? m_crtc->id() : 0); + ret &= m_crtc->atomicAddProperty(req, int(DrmCrtc::PropertyIndex::ModeId), enable ? m_blobId : 0); + ret &= m_crtc->atomicAddProperty(req, int(DrmCrtc::PropertyIndex::Active), enable); + + if (!ret) { + qCWarning(KWIN_DRM) << "Failed to populate atomic modeset"; + return DrmObject::AtomicReturn::Error; + } + if (!m_crtc->propsPending() && !m_conn->propsPending()) { + return DrmObject::AtomicReturn::NoChange; + } + return DrmObject::AtomicReturn::Success; +} } diff --git a/plugins/platforms/drm/egl_gbm_backend.cpp b/plugins/platforms/drm/egl_gbm_backend.cpp --- a/plugins/platforms/drm/egl_gbm_backend.cpp +++ b/plugins/platforms/drm/egl_gbm_backend.cpp @@ -236,10 +236,8 @@ void EglGbmBackend::presentOnOutput(EglGbmBackend::Output &o) { eglSwapBuffers(eglDisplay(), o.eglSurface); - auto oldBuffer = o.buffer; o.buffer = m_backend->createBuffer(o.gbmSurface); m_backend->present(o.buffer, o.output); - delete oldBuffer; if (supportsBufferAge()) { eglQuerySurface(eglDisplay(), o.eglSurface, EGL_BUFFER_AGE_EXT, &o.bufferAge); }