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 @@ -30,6 +30,7 @@ #include #include #include + #include struct gbm_bo; @@ -53,6 +54,7 @@ class UdevMonitor; class DrmOutput; +class DrmPlane; class KWIN_EXPORT DrmBackend : public Platform @@ -78,21 +80,29 @@ int fd() const { return m_fd; } + QVector outputs() const { return m_outputs; } 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,16 +132,17 @@ 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; + QVector m_planes; // all available planes: primarys, cursors and overlays QScopedPointer m_dpmsFilter; KWayland::Server::OutputManagementInterface *m_outputManagement = nullptr; }; - } #endif 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,42 @@ } ); 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]); + + if (DrmPlane *p = new DrmPlane(kplane->plane_id, m_fd)) { + p->setPossibleCrtcs(kplane->possible_crtcs); + p->setFormats(kplane->formats, kplane->count_formats); + m_planes << 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"; @@ -279,6 +319,7 @@ if (m_fd < 0) { return; } + ScopedDrmPointer<_drmModeRes, &drmModeFreeResources> resources(drmModeGetResources(m_fd)); if (!resources) { qCWarning(KWIN_DRM) << "drmModeGetResources failed"; @@ -307,6 +348,7 @@ if (!crtcFound) { continue; } + ScopedDrmPointer<_drmModeCrtc, &drmModeFreeCrtc> crtc(drmModeGetCrtc(m_fd, crtcId)); if (!crtc) { continue; @@ -314,12 +356,22 @@ 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); + drmOutput->m_crtc->setOutput(drmOutput); + drmOutput->m_conn = new DrmConnector(connector->connector_id, m_fd); + drmOutput->m_conn->setOutput(drmOutput); + } + 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; 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,130 @@ +/******************************************************************** + 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; + + 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(); + } + int 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; + } + + int 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 Prop; + QVector m_props; + + uint32_t m_propsPending = 0; + uint32_t m_propsValid = 0; + + virtual int initProps() = 0; // only derived classes know names and quantity of properties + void initProp(int n, drmModeObjectProperties *properties, QVector enumNames = QVector(0)); + + class Prop + { + public: + Prop(drmModePropertyRes *prop, uint64_t val, QVector enumNames); + virtual ~Prop(); + + 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,144 @@ +/******************************************************************** + 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(Prop* 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 (QByteArray(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 Prop(prop, properties->prop_values[i], enumNames); + } + drmModeFreeProperty(prop); + } +} + +int DrmObject::setPropValue(int index, uint64_t new_value) +{ + if( ! (index < m_props.size()) ){ + qCWarning(KWIN_DRM) << "Couldn't set property value" << index << "because there are only" << m_props.size() << "properties."; + return -1; + } +// qCDebug(KWIN_DRM) << m_propsNames[index] << "has new value:" << new_value; //Uncomment for debug spam + m_props[index]->setValue(new_value); + return 0; +} + +int DrmObject::atomicAddProperty(drmModeAtomicReq *req, int prop, uint64_t value) +{ + uint32_t mask = 1U << prop; + if ((m_propsPending | m_propsValid) & mask && value == propValue(prop)) { + return 0; + } + if (drmModeAtomicAddProperty(req, m_id, m_props[prop]->propId(), value) < 0) { + return -1; + } + m_propsPending |= mask; + m_propsValid &= ~mask; + return 0; +} + +/* + * Defintions for struct Prop + */ + +DrmObject::Prop::Prop(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::Prop::~Prop() +{ +} + +void DrmObject::Prop::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; + } + } + + // TODO: only walk this for loop, if debugging enabled + 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,46 @@ +/******************************************************************** + 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(); + + enum propEnum { + CRTC_ID = 0, + PROP_COUNT + }; + + int 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,59 @@ +/******************************************************************** + 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) +{ + qCDebug(KWIN_DRM) << "Creating connector" << connector_id; + + if (initProps()) { + delete this; + } +} + +DrmConnector::~DrmConnector() +{ +} + +int DrmConnector::initProps() +{ + m_propsNames = { + "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 -1; + } + + for (int j = 0; j < PROP_COUNT; ++j) { + initProp(j, properties); + } + drmModeFreeObjectProperties(properties); + return 0; +} + +} 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,49 @@ +/******************************************************************** + 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(); + + enum propEnum { + MODE_ID = 0, + ACTIVE, + PROP_COUNT + }; + + int 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,60 @@ +/******************************************************************** + 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) +{ + qCDebug(KWIN_DRM) << "Creating CRTC" << crtc_id; + + if (initProps()) { + delete this; + } +} + +DrmCrtc::~DrmCrtc() +{ +} + +int DrmCrtc::initProps() +{ + m_propsNames = { + "MODE_ID", + "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 -1; + } + + for (int j = 0; j < PROP_COUNT; ++j) { + initProp(j, properties); + } + drmModeFreeObjectProperties(properties); + return 0; +} + +} 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,105 @@ +/******************************************************************** + 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 propEnum { + TYPE = 0, + SRC_X, + SRC_Y, + SRC_W, + SRC_H, + CRTC_X, + CRTC_Y, + CRTC_W, + CRTC_H, + FB_ID, + CRTC_ID, + PROP_COUNT + }; + + enum typeEnum { + TYPE_PRIMARY = 0, + TYPE_CURSOR, + TYPE_OVERLAY, + TYPE_COUNT + }; + + int initProps(); + typeEnum type(); + bool isCrtcSupported(uint32_t crtc); + int 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,170 @@ +/******************************************************************** + 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) +{ + qCDebug(KWIN_DRM) << "Creating plane" << plane_id; + ScopedDrmPointer<_drmModePlane, &drmModeFreePlane> p(drmModeGetPlane(m_fd, m_id)); + + if (!p) { + qCWarning(KWIN_DRM) << "Failed to get kernel plane" << plane_id; + delete this; + return; + } + + 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()) { + delete this; + } +} + +DrmPlane::~DrmPlane() +{ +} + +int DrmPlane::initProps() +{ + m_propsNames = { + "type", + "SRC_X", + "SRC_Y", + "SRC_W", + "SRC_H", + "CRTC_X", + "CRTC_Y", + "CRTC_W", + "CRTC_H", + "FB_ID", + "CRTC_ID", + }; + + QVector typeNames = { + "Primary", + "Cursor", + "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 -1; + } + + for (int j = 0; j < PROP_COUNT; ++j) { + if (j == TYPE) { + initProp(j, properties, typeNames); + } else { + initProp(j, properties); + } + } + + drmModeFreeObjectProperties(properties); + return 0; +} + +DrmPlane::typeEnum DrmPlane::type() +{ + uint64_t value = propValue(TYPE); + for (int i = 0; i < TYPE_COUNT; i++) { + if (m_props[TYPE]->enumMap(i) == value) { + return static_cast(i); + } + } + return TYPE_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; + } +} + +int DrmPlane::atomicReqPlanePopulate(drmModeAtomicReq *req) +{ + int ret = 0; + + if (m_next) { + setPropValue(DrmPlane::FB_ID, m_next->bufferId()); + } else { + setPropValue(DrmPlane::FB_ID, 0); + setPropValue(DrmPlane::SRC_X, 0); + setPropValue(DrmPlane::SRC_Y, 0); + setPropValue(DrmPlane::SRC_W, 0); + setPropValue(DrmPlane::SRC_H, 0); + setPropValue(DrmPlane::CRTC_X, 0); + setPropValue(DrmPlane::CRTC_Y, 0); + setPropValue(DrmPlane::CRTC_W, 0); + setPropValue(DrmPlane::CRTC_H, 0); + } + + m_propsPending = 0; + + for (int i = DrmPlane::SRC_X; i < DrmPlane::CRTC_ID; i++) { + ret |= atomicAddProperty(req, i, propValue(i)); + } + ret |= atomicAddProperty(req, DrmPlane::CRTC_ID, m_next ? propValue(DrmPlane::CRTC_ID) : 0); + + if (ret) { + qCWarning(KWIN_DRM) << "Failed to populate atomic plane" << m_id; + return -1; + } + if (!m_propsPending) { + return 0; + } + return 1; +} + +} 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 @@ -26,6 +26,7 @@ #include #include #include +#include #include namespace KWayland @@ -44,6 +45,9 @@ class DrmBackend; class DrmBuffer; +class DrmPlane; +class DrmConnector; +class DrmCrtc; class DrmOutput : public QObject { @@ -60,6 +64,8 @@ void hideCursor(); void moveCursor(const QPoint &globalPos); bool present(DrmBuffer *buffer); + bool presentAtomically(DrmBuffer *buffer); + bool presentLegacy(DrmBuffer *buffer); void pageFlipped(); bool init(drmModeConnector *connector); void restoreSaved(); @@ -75,6 +81,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 +104,17 @@ friend class DrmBackend; DrmOutput(DrmBackend *backend); void cleanupBlackBuffer(); - bool setMode(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); + bool initPrimaryPlane(); + bool initCursorPlane(); + int atomicReqModesetPopulate(drmModeAtomicReq *req, bool enable); + DrmBackend *m_backend; QPoint m_globalPos; quint32 m_crtcId = 0; @@ -112,10 +123,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 +138,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,44 +104,27 @@ return QRect(m_globalPos, size()); } -bool DrmOutput::present(DrmBuffer *buffer) +void DrmOutput::pageFlipped() { - 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; + if (m_backend->atomicModeSetting()){ + foreach (DrmPlane *p, m_planesFlipList) { + if( p->current() && p->current() != p->next() ) { + delete p->current(); + } + p->setCurrent(p->next()); + p->setNext(nullptr); } + m_planesFlipList.clear(); } - 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; + else{ + if (!m_nextBuffer) + return; + if( m_currentBuffer && m_currentBuffer != m_nextBuffer ){ + delete m_currentBuffer; + } + m_currentBuffer = m_nextBuffer; + m_nextBuffer = nullptr; } - m_currentBuffer->releaseGbm(); - m_currentBuffer = nullptr; cleanupBlackBuffer(); } @@ -185,6 +175,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 +235,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; @@ -331,10 +330,11 @@ } m_blackBuffer->image()->fill(Qt::black); } - return setMode(m_blackBuffer); + // TODO: Do this atomically + return setModeLegacy(m_blackBuffer); } -bool DrmOutput::setMode(DrmBuffer *buffer) +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(); @@ -505,6 +505,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::TYPE_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::TYPE_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 +580,32 @@ 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) < 0) { + 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 +696,183 @@ return true; } +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; + } + + int ret = 0; + 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 < 0){ + drmModeAtomicFree(req); + delete buffer; + return false; + } + if (ret > 0) { + 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 < 0) { + drmModeAtomicFree(req); + m_primaryPlane->setNext(nullptr); + m_planesFlipList.clear(); + delete buffer; + return false; + } + if (ret > 0) { + 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) < 0) { + 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; +} + +int DrmOutput::atomicReqModesetPopulate(drmModeAtomicReq *req, bool enable) +{ + if (enable) { + m_primaryPlane->setPropValue(DrmPlane::SRC_W, m_mode.hdisplay << 16); + m_primaryPlane->setPropValue(DrmPlane::SRC_H, m_mode.vdisplay << 16); + m_primaryPlane->setPropValue(DrmPlane::CRTC_W, m_mode.hdisplay); + m_primaryPlane->setPropValue(DrmPlane::CRTC_H, m_mode.vdisplay); + } else { + m_primaryPlane->setPropValue(DrmPlane::SRC_W, 0); + m_primaryPlane->setPropValue(DrmPlane::SRC_H, 0); + m_primaryPlane->setPropValue(DrmPlane::CRTC_W, 0); + m_primaryPlane->setPropValue(DrmPlane::CRTC_H, 0); + } + + int ret = 0; + + m_crtc->setPropsPending(0); + m_conn->setPropsPending(0); + + ret |= m_conn->atomicAddProperty(req, DrmConnector::CRTC_ID, enable ? m_crtc->id() : 0); + ret |= m_crtc->atomicAddProperty(req, DrmCrtc::MODE_ID, enable ? m_blobId : 0); + ret |= m_crtc->atomicAddProperty(req, DrmCrtc::ACTIVE, enable); + + if (ret) { + qCWarning(KWIN_DRM) << "Failed to populate atomic modeset"; + return -1; + } + if (!m_crtc->propsPending() && !m_conn->propsPending()) { + return 0; + } + return 1; +} } 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); }