[DRM plugin] Correct Atomic Mode Setting

This patch makes the AMS execution path work with the new DrmCrtc and
DrmBuffer structure and solves major issues about:
* VT switching
* DPMS
* Hot plugging
* Logout
* Memory leaks

Test Plan:
Tested with Gl and QPainter.

Reviewers: #kwin

Subscribers: kwin, #kwin

Tags: #kwin

Differential Revision: https://phabricator.kde.org/D5191
This commit is contained in:
Roman Gilg 2017-05-09 21:29:10 +02:00
parent efedddd905
commit d15cb52682
12 changed files with 430 additions and 343 deletions

View file

@ -154,8 +154,10 @@ void DrmBackend::checkOutputsAreOn()
void DrmBackend::activate(bool active)
{
if (active) {
qCDebug(KWIN_DRM) << "Activating session.";
reactivate();
} else {
qCDebug(KWIN_DRM) << "Deactivating session.";
deactivate();
}
}
@ -171,7 +173,9 @@ void DrmBackend::reactivate()
const QPoint cp = Cursor::pos() - softwareCursorHotspot();
for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) {
DrmOutput *o = *it;
o->pageFlipped();
// only relevant in atomic mode
o->m_modesetRequested = true;
o->pageFlipped(); // TODO: Do we really need this?
o->m_crtc->blank();
o->showCursor(c);
o->moveCursor(cp);
@ -214,6 +218,12 @@ void DrmBackend::pageFlipHandler(int fd, unsigned int frame, unsigned int sec, u
if (output->m_backend->m_pageFlipsPending == 0) {
// TODO: improve, this currently means we wait for all page flips or all outputs.
// It would be better to driver the repaint per output
if (output->m_dpmsAtomicOffPending) {
output->m_modesetRequested = true;
output->dpmsAtomicOff();
}
if (Compositor::self()) {
Compositor::self()->bufferSwapComplete();
}
@ -268,12 +278,12 @@ void DrmBackend::openDrm()
// 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);
DrmPlane *p = new DrmPlane(kplane->plane_id, this);
if (p->atomicInit()) {
m_planes << p;
if (p->type() == DrmPlane::TypeIndex::Overlay) {
m_overlayPlanes << p;
}
} else {
delete p;
}
@ -297,26 +307,26 @@ void DrmBackend::openDrm()
}
for (int i = 0; i < res->count_connectors; ++i) {
m_connectors << new DrmConnector(res->connectors[i], m_fd);
m_connectors << new DrmConnector(res->connectors[i], this);
}
for (int i = 0; i < res->count_crtcs; ++i) {
m_crtcs << new DrmCrtc(res->crtcs[i], m_fd, i);
m_crtcs << new DrmCrtc(res->crtcs[i], this, i);
}
if (m_atomicModeSetting) {
auto tryInit = [] (DrmObject *o) -> bool {
if (o->init()) {
auto tryAtomicInit = [] (DrmObject *obj) -> bool {
if (obj->atomicInit()) {
return false;
} else {
delete o;
delete obj;
return true;
}
};
m_connectors.erase(std::remove_if(m_connectors.begin(), m_connectors.end(), tryInit), m_connectors.end());
m_crtcs.erase(std::remove_if(m_crtcs.begin(), m_crtcs.end(), tryInit), m_crtcs.end());
m_connectors.erase(std::remove_if(m_connectors.begin(), m_connectors.end(), tryAtomicInit), m_connectors.end());
m_crtcs.erase(std::remove_if(m_crtcs.begin(), m_crtcs.end(), tryAtomicInit), m_crtcs.end());
}
queryResources();
updateOutputs();
if (m_outputs.isEmpty()) {
qCWarning(KWIN_DRM) << "No outputs, cannot render, will terminate now";
@ -341,7 +351,7 @@ void DrmBackend::openDrm()
}
if (device->hasProperty("HOTPLUG", "1")) {
qCDebug(KWIN_DRM) << "Received hot plug event for monitored drm device";
queryResources();
updateOutputs();
m_cursorIndex = (m_cursorIndex + 1) % 2;
updateCursor();
}
@ -355,7 +365,7 @@ void DrmBackend::openDrm()
initCursor();
}
void DrmBackend::queryResources()
void DrmBackend::updateOutputs()
{
if (m_fd < 0) {
return;
@ -717,5 +727,4 @@ void DrmBackend::outputDpmsChanged()
setOutputsEnabled(enabled);
}
}

View file

@ -90,6 +90,9 @@ public:
QVector<DrmPlane*> planes() const {
return m_planes;
}
QVector<DrmPlane*> overlayPlanes() const {
return m_overlayPlanes;
}
void outputWentOff();
void checkOutputsAreOn();
@ -128,7 +131,7 @@ private:
void activate(bool active);
void reactivate();
void deactivate();
void queryResources();
void updateOutputs();
void setCursor();
void updateCursor();
void moveCursor();
@ -157,6 +160,7 @@ private:
bool m_active = false;
// all available planes: primarys, cursors and overlays
QVector<DrmPlane*> m_planes;
QVector<DrmPlane*> m_overlayPlanes;
QScopedPointer<DpmsInputEventFilter> m_dpmsFilter;
KWayland::Server::OutputManagementInterface *m_outputManagement = nullptr;
gbm_device *m_gbmDevice = nullptr;

View file

@ -18,6 +18,8 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "drm_object.h"
#include "drm_backend.h"
#include "logging.h"
namespace KWin
@ -27,8 +29,8 @@ namespace KWin
* Defintions for class DrmObject
*/
DrmObject::DrmObject(uint32_t object_id, int fd)
: m_fd(fd)
DrmObject::DrmObject(uint32_t object_id, DrmBackend *backend)
: m_backend(backend)
, m_id(object_id)
{
}
@ -43,7 +45,7 @@ void DrmObject::initProp(int n, drmModeObjectProperties *properties, QVector<QBy
{
m_props.resize(m_propsNames.size());
for (unsigned int i = 0; i < properties->count_props; ++i) {
drmModePropertyRes *prop = drmModeGetProperty(m_fd, properties->props[i]);
drmModePropertyRes *prop = drmModeGetProperty(m_backend->fd(), properties->props[i]);
if (!prop) {
continue;
}
@ -56,27 +58,27 @@ void DrmObject::initProp(int n, drmModeObjectProperties *properties, QVector<QBy
}
}
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
if (drmModeAtomicAddProperty(req, m_id, m_props[prop]->propId(), value) <= 0) {
qCWarning(KWIN_DRM) << "Adding property" << m_propsNames[prop] << "to atomic commit failed for object" << this;
return false;
}
return true;
}
bool DrmObject::atomicPopulate(drmModeAtomicReq *req)
{
bool ret = true;
for (int i = 0; i < m_props.size(); i++) {
ret &= atomicAddProperty(req, i, m_props[i]->value());
}
if (!ret) {
qCWarning(KWIN_DRM) << "Failed to populate atomic plane" << m_id;
return false;
}
m_propsPending |= mask;
m_propsValid &= ~mask;
// adding property was successful
return true;
}

View file

@ -30,73 +30,58 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
namespace KWin
{
class DrmBackend;
class DrmOutput;
class DrmObject
{
public:
// creates drm object by its id delivered by the kernel
DrmObject(uint32_t object_id, int fd);
DrmObject(uint32_t object_id, DrmBackend *backend);
virtual ~DrmObject() = 0;
enum class AtomicReturn {
NoChange,
Success,
Error
};
virtual bool init() = 0;
virtual bool atomicInit() = 0;
uint32_t id() const {
return m_id;
}
DrmOutput* output() const {
DrmOutput *output() const {
return m_output;
}
void setOutput(DrmOutput* output) {
m_output = output;
}
uint32_t propId(int index) {
return m_props[index]->propId();
uint32_t propId(int prop) {
return m_props[prop]->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;
uint64_t value(int prop) {
return m_props[prop]->value();
}
bool atomicAddProperty(drmModeAtomicReq *req, int prop, uint64_t value);
void setValue(int prop, uint64_t new_value)
{
Q_ASSERT(prop < m_props.size());
m_props[prop]->setValue(new_value);
}
virtual bool atomicPopulate(drmModeAtomicReq *req);
protected:
const int m_fd = 0;
const uint32_t m_id = 0;
DrmOutput *m_output = nullptr;
QVector<QByteArray> m_propsNames; // for comparision with received name of DRM object
class Property;
QVector<Property*> 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<QByteArray> enumNames = QVector<QByteArray>(0));
bool atomicAddProperty(drmModeAtomicReq *req, int prop, uint64_t value);
DrmBackend *m_backend;
const uint32_t m_id = 0;
DrmOutput *m_output = nullptr;
// for comparision with received name of DRM object
QVector<QByteArray> m_propsNames;
class Property;
QVector<Property *> m_props;
class Property
{
@ -113,7 +98,7 @@ protected:
uint32_t propId() {
return m_propId;
}
uint32_t value() {
uint64_t value() {
return m_value;
}
void setValue(uint64_t new_value) {

View file

@ -18,16 +18,17 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "drm_object_connector.h"
#include "drm_backend.h"
#include "drm_pointer.h"
#include "logging.h"
namespace KWin
{
DrmConnector::DrmConnector(uint32_t connector_id, int fd)
: DrmObject(connector_id, fd)
DrmConnector::DrmConnector(uint32_t connector_id, DrmBackend *backend)
: DrmObject(connector_id, backend)
{
ScopedDrmPointer<_drmModeConnector, &drmModeFreeConnector> con(drmModeGetConnector(fd, connector_id));
ScopedDrmPointer<_drmModeConnector, &drmModeFreeConnector> con(drmModeGetConnector(backend->fd(), connector_id));
if (!con) {
return;
}
@ -38,7 +39,7 @@ DrmConnector::DrmConnector(uint32_t connector_id, int fd)
DrmConnector::~DrmConnector() = default;
bool DrmConnector::init()
bool DrmConnector::atomicInit()
{
qCDebug(KWIN_DRM) << "Creating connector" << m_id;
@ -54,7 +55,7 @@ bool DrmConnector::initProps()
QByteArrayLiteral("CRTC_ID"),
};
drmModeObjectProperties *properties = drmModeObjectGetProperties(m_fd, m_id, DRM_MODE_OBJECT_CONNECTOR);
drmModeObjectProperties *properties = drmModeObjectGetProperties(m_backend->fd(), m_id, DRM_MODE_OBJECT_CONNECTOR);
if (!properties) {
qCWarning(KWIN_DRM) << "Failed to get properties for connector " << m_id ;
return false;
@ -70,7 +71,7 @@ bool DrmConnector::initProps()
bool DrmConnector::isConnected()
{
ScopedDrmPointer<_drmModeConnector, &drmModeFreeConnector> con(drmModeGetConnector(m_fd, m_id));
ScopedDrmPointer<_drmModeConnector, &drmModeFreeConnector> con(drmModeGetConnector(m_backend->fd(), m_id));
if (!con) {
return false;
}

View file

@ -28,11 +28,11 @@ namespace KWin
class DrmConnector : public DrmObject
{
public:
DrmConnector(uint32_t connector_id, int fd);
DrmConnector(uint32_t connector_id, DrmBackend *backend);
virtual ~DrmConnector();
bool init();
bool atomicInit();
enum class PropertyIndex {
CrtcId = 0,

View file

@ -26,8 +26,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
namespace KWin
{
DrmCrtc::DrmCrtc(uint32_t crtc_id, int fd, int resIndex)
: DrmObject(crtc_id, fd),
DrmCrtc::DrmCrtc(uint32_t crtc_id, DrmBackend *backend, int resIndex)
: DrmObject(crtc_id, backend),
m_resIndex(resIndex)
{
}
@ -36,7 +36,7 @@ DrmCrtc::~DrmCrtc()
{
}
bool DrmCrtc::init()
bool DrmCrtc::atomicInit()
{
qCDebug(KWIN_DRM) << "Atomic init for CRTC:" << resIndex() << "id:" << m_id;
@ -53,7 +53,7 @@ bool DrmCrtc::initProps()
QByteArrayLiteral("ACTIVE"),
};
drmModeObjectProperties *properties = drmModeObjectGetProperties(m_fd, m_id, DRM_MODE_OBJECT_CRTC);
drmModeObjectProperties *properties = drmModeObjectGetProperties(m_backend->fd(), m_id, DRM_MODE_OBJECT_CRTC);
if (!properties) {
qCWarning(KWIN_DRM) << "Failed to get properties for crtc " << m_id ;
return false;
@ -69,7 +69,7 @@ bool DrmCrtc::initProps()
void DrmCrtc::flipBuffer()
{
if (m_currentBuffer && m_output->m_backend->deleteBufferAfterPageFlip() && m_currentBuffer != m_nextBuffer) {
if (m_currentBuffer && m_backend->deleteBufferAfterPageFlip() && m_currentBuffer != m_nextBuffer) {
delete m_currentBuffer;
}
m_currentBuffer = m_nextBuffer;
@ -82,7 +82,7 @@ void DrmCrtc::flipBuffer()
bool DrmCrtc::blank()
{
if (!m_blackBuffer) {
DrmDumbBuffer *blackBuffer = m_output->m_backend->createBuffer(m_output->pixelSize());
DrmDumbBuffer *blackBuffer = m_backend->createBuffer(m_output->pixelSize());
if (!blackBuffer->map()) {
delete blackBuffer;
return false;
@ -91,9 +91,8 @@ bool DrmCrtc::blank()
m_blackBuffer = blackBuffer;
}
// TODO: Do this atomically
if (m_output->setModeLegacy(m_blackBuffer)) {
if (m_currentBuffer && m_output->m_backend->deleteBufferAfterPageFlip()) {
if (m_currentBuffer && m_backend->deleteBufferAfterPageFlip()) {
delete m_currentBuffer;
delete m_nextBuffer;
}

View file

@ -32,11 +32,11 @@ class DrmDumbBuffer;
class DrmCrtc : public DrmObject
{
public:
DrmCrtc(uint32_t crtc_id, int fd, int resIndex);
DrmCrtc(uint32_t crtc_id, DrmBackend *backend, int resIndex);
virtual ~DrmCrtc();
bool init();
bool atomicInit();
enum class PropertyIndex {
ModeId = 0,
@ -64,7 +64,6 @@ public:
bool blank();
private:
DrmBackend *m_backend;
int m_resIndex;
DrmBuffer *m_currentBuffer = nullptr;

View file

@ -18,6 +18,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "drm_object_plane.h"
#include "drm_backend.h"
#include "drm_buffer.h"
#include "drm_pointer.h"
#include "logging.h"
@ -25,17 +26,21 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
namespace KWin
{
DrmPlane::DrmPlane(uint32_t plane_id, int fd)
: DrmObject(plane_id, fd)
DrmPlane::DrmPlane(uint32_t plane_id, DrmBackend *backend)
: DrmObject(plane_id, backend)
{
}
DrmPlane::~DrmPlane() = default;
bool DrmPlane::init()
DrmPlane::~DrmPlane()
{
qCDebug(KWIN_DRM) << "Initialize plane" << m_id;
ScopedDrmPointer<_drmModePlane, &drmModeFreePlane> p(drmModeGetPlane(m_fd, m_id));
delete m_current;
delete m_next;
}
bool DrmPlane::atomicInit()
{
qCDebug(KWIN_DRM) << "Atomic init for plane:" << m_id;
ScopedDrmPointer<_drmModePlane, &drmModeFreePlane> p(drmModeGetPlane(m_backend->fd(), m_id));
if (!p) {
qCWarning(KWIN_DRM) << "Failed to get kernel plane" << m_id;
@ -44,8 +49,9 @@ bool DrmPlane::init()
m_possibleCrtcs = p->possible_crtcs;
m_formats.resize(p->count_formats);
for (int i = 0; i < p->count_formats; i++) {
int count_formats = p->count_formats;
m_formats.resize(count_formats);
for (int i = 0; i < count_formats; i++) {
m_formats[i] = p->formats[i];
}
@ -77,7 +83,7 @@ bool DrmPlane::initProps()
QByteArrayLiteral("Overlay"),
};
drmModeObjectProperties *properties = drmModeObjectGetProperties(m_fd, m_id, DRM_MODE_OBJECT_PLANE);
drmModeObjectProperties *properties = drmModeObjectGetProperties(m_backend->fd(), m_id, DRM_MODE_OBJECT_PLANE);
if (!properties){
qCWarning(KWIN_DRM) << "Failed to get properties for plane " << m_id ;
return false;
@ -98,77 +104,48 @@ bool DrmPlane::initProps()
DrmPlane::TypeIndex DrmPlane::type()
{
uint64_t value = propValue(int(PropertyIndex::Type));
uint64_t v = value(int(PropertyIndex::Type));
int typeCount = int(TypeIndex::Count);
for (int i = 0; i < typeCount; i++) {
if (m_props[int(PropertyIndex::Type)]->enumMap(i) == value) {
if (m_props[int(PropertyIndex::Type)]->enumMap(i) == v) {
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::setNext(DrmBuffer *b){
setValue(int(PropertyIndex::FbId), b ? b->bufferId() : 0);
m_next = b;
}
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 DrmPlane::atomicPopulate(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);
for (int i = 1; i < m_props.size(); i++) {
ret &= atomicAddProperty(req, i, m_props[i]->value());
}
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;
return false;
}
if (!m_propsPending) {
return DrmObject::AtomicReturn::NoChange;
return true;
}
void DrmPlane::flipBuffer()
{
m_current = m_next;
m_next = nullptr;
}
void DrmPlane::flipBufferWithDelete()
{
if (m_current != m_next) {
delete m_current;
}
return DrmObject::AtomicReturn::Success;
flipBuffer();
}
}

View file

@ -32,9 +32,9 @@ class DrmBuffer;
class DrmPlane : public DrmObject
{
public:
DrmPlane(uint32_t plane_id, int fd);
DrmPlane(uint32_t plane_id, DrmBackend *backend);
virtual ~DrmPlane();
~DrmPlane();
enum class PropertyIndex {
Type = 0,
@ -58,36 +58,31 @@ public:
Count
};
bool init();
bool atomicInit();
bool initProps();
TypeIndex type();
bool isCrtcSupported(uint32_t crtc);
DrmObject::AtomicReturn atomicReqPlanePopulate(drmModeAtomicReq *req);
DrmBuffer *current(){
return m_current;
bool isCrtcSupported(int resIndex) const {
return (m_possibleCrtcs & (1 << resIndex));
}
DrmBuffer *next(){
return m_next;
}
void setCurrent(DrmBuffer *b){
m_current = b;
}
void setNext(DrmBuffer *b){
m_next = b;
}
QVector<uint32_t> formats(){
QVector<uint32_t> formats() const {
return m_formats;
}
void setFormats(uint32_t const *f, int fcount);
void setPossibleCrtcs(uint32_t value){
m_possibleCrtcs = value;
DrmBuffer *current() const {
return m_current;
}
uint32_t possibleCrtcs(){
return m_possibleCrtcs;
DrmBuffer *next() const {
return m_next;
}
void setCurrent(DrmBuffer *b) {
m_current = b;
}
void setNext(DrmBuffer *b);
bool atomicPopulate(drmModeAtomicReq *req);
void flipBuffer();
void flipBufferWithDelete();
private:
DrmBuffer *m_current = nullptr;

View file

@ -63,6 +63,20 @@ DrmOutput::~DrmOutput()
{
hideCursor();
m_crtc->blank();
if (m_primaryPlane) {
// TODO: when having multiple planes, also clean up these
m_primaryPlane->setOutput(nullptr);
if (m_backend->deleteBufferAfterPageFlip()) {
delete m_primaryPlane->current();
}
m_primaryPlane->setCurrent(nullptr);
}
m_crtc->setOutput(nullptr);
m_conn->setOutput(nullptr);
delete m_waylandOutput.data();
delete m_waylandOutputDevice.data();
}
@ -72,10 +86,8 @@ void DrmOutput::releaseGbm()
if (DrmBuffer *b = m_crtc->current()) {
b->releaseGbm();
}
if (m_primaryPlane) {
if (m_primaryPlane->current()) {
m_primaryPlane->current()->releaseGbm();
}
if (m_primaryPlane && m_primaryPlane->current()) {
m_primaryPlane->current()->releaseGbm();
}
}
@ -176,10 +188,10 @@ bool DrmOutput::init(drmModeConnector *connector)
if (!initPrimaryPlane()) {
return false;
}
}
if (!m_crtc->blank()) {
} else if (!m_crtc->blank()) {
return false;
}
setDpms(DpmsMode::On);
if (!m_waylandOutput.isNull()) {
delete m_waylandOutput.data();
@ -496,7 +508,7 @@ bool DrmOutput::initPrimaryPlane()
if (m_primaryPlane) { // Output already has a primary plane
continue;
}
if (!p->isCrtcSupported(m_crtc->id())) {
if (!p->isCrtcSupported(m_crtc->resIndex())) {
continue;
}
p->setOutput(this);
@ -524,7 +536,7 @@ bool DrmOutput::initCursorPlane() // TODO: Add call in init (but needs lay
if (m_cursorPlane) { // Output already has a cursor plane
continue;
}
if (!p->isCrtcSupported(m_crtc->id())) {
if (!p->isCrtcSupported(m_crtc->resIndex())) {
continue;
}
p->setOutput(this);
@ -554,45 +566,70 @@ void DrmOutput::setDpms(DrmOutput::DpmsMode mode)
if (m_dpms.isNull()) {
return;
}
if (mode == m_dpmsMode) {
if (mode == m_dpmsModePending) {
qCDebug(KWIN_DRM) << "New DPMS mode equals old mode. DPMS unchanged.";
return;
}
if (m_backend->atomicModeSetting()) {
drmModeAtomicReq *req = drmModeAtomicAlloc();
m_dpmsModePending = mode;
if (atomicReqModesetPopulate(req, mode == DpmsMode::On) == DrmObject::AtomicReturn::Error) {
qCWarning(KWIN_DRM) << "Failed to populate atomic request for output" << m_crtc->id();
return;
}
if (drmModeAtomicCommit(m_backend->fd(), req, DRM_MODE_ATOMIC_ALLOW_MODESET, this)) {
qCWarning(KWIN_DRM) << "Failed to commit atomic request for output" << m_crtc->id();
if (m_backend->atomicModeSetting()) {
m_modesetRequested = true;
if (mode == DpmsMode::On) {
if (m_pageFlipPending) {
m_pageFlipPending = false;
Compositor::self()->bufferSwapComplete();
}
dpmsOnHandler();
} else {
qCDebug(KWIN_DRM) << "DPMS set for output" << m_crtc->id();
m_dpmsAtomicOffPending = true;
if (!m_pageFlipPending) {
dpmsAtomicOff();
}
}
drmModeAtomicFree(req);
} else {
if (drmModeConnectorSetProperty(m_backend->fd(), m_conn->id(), m_dpms->prop_id, uint64_t(mode)) < 0) {
m_dpmsModePending = m_dpmsMode;
qCWarning(KWIN_DRM) << "Setting DPMS failed";
return;
}
if (mode == DpmsMode::On) {
dpmsOnHandler();
} else {
dpmsOffHandler();
}
m_dpmsMode = m_dpmsModePending;
}
}
void DrmOutput::dpmsOnHandler()
{
qCDebug(KWIN_DRM) << "DPMS mode set for output" << m_crtc->id() << "to On.";
m_dpmsMode = mode;
if (m_waylandOutput) {
m_waylandOutput->setDpmsMode(toWaylandDpmsMode(m_dpmsMode));
m_waylandOutput->setDpmsMode(toWaylandDpmsMode(m_dpmsModePending));
}
emit dpmsChanged();
if (m_dpmsMode != DpmsMode::On) {
m_backend->outputWentOff();
} else {
m_backend->checkOutputsAreOn();
m_backend->checkOutputsAreOn();
if (!m_backend->atomicModeSetting()) {
m_crtc->blank();
if (Compositor *compositor = Compositor::self()) {
compositor->addRepaintFull();
}
}
if (Compositor *compositor = Compositor::self()) {
compositor->addRepaintFull();
}
}
void DrmOutput::dpmsOffHandler()
{
qCDebug(KWIN_DRM) << "DPMS mode set for output" << m_crtc->id() << "to Off.";
if (m_waylandOutput) {
m_waylandOutput->setDpmsMode(toWaylandDpmsMode(m_dpmsModePending));
}
emit dpmsChanged();
m_backend->outputWentOff();
}
QString DrmOutput::name() const
@ -681,36 +718,51 @@ bool DrmOutput::commitChanges()
void DrmOutput::pageFlipped()
{
m_pageFlipPending = false;
if (!m_crtc) {
return;
}
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_crtc->next()) {
// on manual vt switch
if (DrmBuffer *b = m_crtc->current()) {
b->releaseGbm();
// Egl based surface buffers get destroyed, QPainter based dumb buffers not
// TODO: split up DrmOutput in two for dumb and egl/gbm surface buffer compatible subclasses completely?
if (m_backend->deleteBufferAfterPageFlip()) {
if (m_backend->atomicModeSetting()) {
if (!m_primaryPlane->next()) {
// on manual vt switch
// TODO: when we later use overlay planes it might happen, that we have a page flip with only
// damage on one of these, and therefore the primary plane has no next buffer
// -> Then we don't want to return here!
if (m_primaryPlane->current()) {
m_primaryPlane->current()->releaseGbm();
}
return;
}
return;
for (DrmPlane *p : m_nextPlanesFlipList) {
p->flipBufferWithDelete();
}
m_nextPlanesFlipList.clear();
} else {
if (!m_crtc->next()) {
// on manual vt switch
if (DrmBuffer *b = m_crtc->current()) {
b->releaseGbm();
}
}
m_crtc->flipBuffer();
}
} else {
if (m_backend->atomicModeSetting()){
for (DrmPlane *p : m_nextPlanesFlipList) {
p->flipBuffer();
}
m_nextPlanesFlipList.clear();
} else {
m_crtc->flipBuffer();
}
m_crtc->flipBuffer();
}
}
void DrmOutput::pageFlippedBufferRemover(DrmBuffer *oldbuffer, DrmBuffer *newbuffer)
{
if (oldbuffer && m_backend->deleteBufferAfterPageFlip() && oldbuffer != newbuffer) {
delete oldbuffer;
}
}
bool DrmOutput::present(DrmBuffer *buffer)
{
if (!buffer || buffer->bufferId() == 0) {
@ -723,110 +775,61 @@ bool DrmOutput::present(DrmBuffer *buffer)
}
}
bool DrmOutput::dpmsAtomicOff()
{
m_dpmsAtomicOffPending = false;
// TODO: With multiple planes: deactivate all of them here
delete m_primaryPlane->next();
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();
dpmsOffHandler();
return true;
}
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()) {
if (m_pageFlipPending) {
qCWarning(KWIN_DRM) << "Page not yet flipped.";
return false;
}
DrmObject::AtomicReturn ret;
uint32_t flags = DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT;
m_primaryPlane->setNext(buffer);
m_nextPlanesFlipList << m_primaryPlane;
// 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";
if (!doAtomicCommit(AtomicCommitMode::Test)) {
//TODO: When we use planes for layered rendering, fallback to renderer instead. Also for direct scanout?
qCDebug(KWIN_DRM) << "Atomic test commit failed. Aborting present.";
if (this->m_backend->deleteBufferAfterPageFlip()) {
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());
if (!doAtomicCommit(AtomicCommitMode::Real)) {
qCDebug(KWIN_DRM) << "Atomic commit failed. This should have never happened! Aborting present.";
return false;
}
foreach (DrmPlane* p, m_planesFlipList) {
p->setPropsValid(p->propsValid() | p->propsPending());
}
drmModeAtomicFree(req);
m_pageFlipPending = true;
return true;
}
bool DrmOutput::presentLegacy(DrmBuffer *buffer)
{
if (m_crtc->next()) {
@ -869,37 +872,135 @@ bool DrmOutput::setModeLegacy(DrmBuffer *buffer)
}
}
DrmObject::AtomicReturn DrmOutput::atomicReqModesetPopulate(drmModeAtomicReq *req, bool enable)
bool DrmOutput::doAtomicCommit(AtomicCommitMode mode)
{
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);
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 (m_dpmsMode != m_dpmsModePending) {
qCWarning(KWIN_DRM) << "Setting DPMS failed";
m_dpmsModePending = m_dpmsMode;
if (m_dpmsMode != DpmsMode::On) {
dpmsOffHandler();
}
}
// 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_backend->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;
}
flags |= DRM_MODE_PAGE_FLIP_EVENT;
}
} 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);
flags |= DRM_MODE_ATOMIC_TEST_ONLY;
}
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);
// 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 modeset";
return DrmObject::AtomicReturn::Error;
qCWarning(KWIN_DRM) << "Failed to populate atomic planes. Abort atomic commit!";
errorHandler();
return false;
}
if (!m_crtc->propsPending() && !m_conn->propsPending()) {
return DrmObject::AtomicReturn::NoChange;
if (drmModeAtomicCommit(m_backend->fd(), req, flags, this)) {
qCWarning(KWIN_DRM) << "Atomic request failed to commit:" << strerror(errno);
errorHandler();
return false;
}
return DrmObject::AtomicReturn::Success;
if (mode == AtomicCommitMode::Real && (flags & DRM_MODE_ATOMIC_ALLOW_MODESET)) {
qCDebug(KWIN_DRM) << "Atomic Modeset successful.";
m_modesetRequested = false;
m_dpmsMode = m_dpmsModePending;
}
drmModeAtomicFree(req);
return true;
}
bool DrmOutput::atomicReqModesetPopulate(drmModeAtomicReq *req, bool enable)
{
if (enable) {
m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcX), 0);
m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcY), 0);
m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcW), m_mode.hdisplay << 16);
m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcH), m_mode.vdisplay << 16);
m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcW), m_mode.hdisplay);
m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcH), m_mode.vdisplay);
m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcId), m_crtc->id());
} else {
if (m_backend->deleteBufferAfterPageFlip()) {
delete m_primaryPlane->current();
delete m_primaryPlane->next();
}
m_primaryPlane->setCurrent(nullptr);
m_primaryPlane->setNext(nullptr);
m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcX), 0);
m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcY), 0);
m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcW), 0);
m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcH), 0);
m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcW), 0);
m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcH), 0);
m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcId), 0);
}
m_conn->setValue(int(DrmConnector::PropertyIndex::CrtcId), enable ? m_crtc->id() : 0);
m_crtc->setValue(int(DrmCrtc::PropertyIndex::ModeId), enable ? m_blobId : 0);
m_crtc->setValue(int(DrmCrtc::PropertyIndex::Active), enable);
bool ret = true;
ret &= m_conn->atomicPopulate(req);
ret &= m_crtc->atomicPopulate(req);
return ret;
}
}

View file

@ -95,7 +95,8 @@ public:
};
void setDpms(DpmsMode mode);
bool isDpmsEnabled() const {
return m_dpmsMode == DpmsMode::On;
// We care for current as well as pending mode in order to allow first present in AMS.
return m_dpmsModePending == DpmsMode::On;
}
QByteArray uuid() const {
@ -111,6 +112,13 @@ private:
// and save the connector ids in the DrmCrtc instance.
DrmOutput(DrmBackend *backend);
bool presentAtomically(DrmBuffer *buffer);
enum class AtomicCommitMode {
Test,
Real
};
bool doAtomicCommit(AtomicCommitMode mode);
bool presentLegacy(DrmBuffer *buffer);
bool setModeLegacy(DrmBuffer *buffer);
void initEdid(drmModeConnector *connector);
@ -120,10 +128,13 @@ private:
void setGlobalPos(const QPoint &pos);
void setScale(qreal scale);
void pageFlippedBufferRemover(DrmBuffer *oldbuffer, DrmBuffer *newbuffer);
bool initPrimaryPlane();
bool initCursorPlane();
DrmObject::AtomicReturn atomicReqModesetPopulate(drmModeAtomicReq *req, bool enable);
void dpmsOnHandler();
void dpmsOffHandler();
bool dpmsAtomicOff();
bool atomicReqModesetPopulate(drmModeAtomicReq *req, bool enable);
DrmBackend *m_backend;
DrmConnector *m_conn = nullptr;
@ -138,12 +149,16 @@ private:
QPointer<KWayland::Server::OutputChangeSet> m_changeset;
KWin::ScopedDrmPointer<_drmModeProperty, &drmModeFreeProperty> m_dpms;
DpmsMode m_dpmsMode = DpmsMode::On;
DpmsMode m_dpmsModePending = DpmsMode::On;
QByteArray m_uuid;
uint32_t m_blobId = 0;
DrmPlane* m_primaryPlane = nullptr;
DrmPlane* m_cursorPlane = nullptr;
QVector<DrmPlane*> m_planesFlipList;
QVector<DrmPlane*> m_nextPlanesFlipList;
bool m_pageFlipPending = false;
bool m_dpmsAtomicOffPending = false;
bool m_modesetRequested = true;
};
}