diff --git a/plugins/platforms/drm/drm_backend.cpp b/plugins/platforms/drm/drm_backend.cpp index cb15ad23e..37c5d0fef 100644 --- a/plugins/platforms/drm/drm_backend.cpp +++ b/plugins/platforms/drm/drm_backend.cpp @@ -1,721 +1,730 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin 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_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" #include "logind.h" #include "main.h" #include "scene_qpainter_drm_backend.h" #include "screens_drm.h" #include "udev.h" #include "wayland_server.h" #if HAVE_GBM #include "egl_gbm_backend.h" #include #endif // KWayland #include #include // KF5 #include #include #include // Qt #include #include #include // system #include // drm #include #include #include #ifndef DRM_CAP_CURSOR_WIDTH #define DRM_CAP_CURSOR_WIDTH 0x8 #endif #ifndef DRM_CAP_CURSOR_HEIGHT #define DRM_CAP_CURSOR_HEIGHT 0x9 #endif #define KWIN_DRM_EVENT_CONTEXT_VERSION 2 namespace KWin { DrmBackend::DrmBackend(QObject *parent) : Platform(parent) , m_udev(new Udev) , m_udevMonitor(m_udev->monitor()) , m_dpmsFilter() { handleOutputs(); m_cursor[0] = nullptr; m_cursor[1] = nullptr; } DrmBackend::~DrmBackend() { #if HAVE_GBM if (m_gbmDevice) { gbm_device_destroy(m_gbmDevice); } #endif if (m_fd >= 0) { // wait for pageflips while (m_pageFlipsPending != 0) { QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents); } qDeleteAll(m_outputs); qDeleteAll(m_planes); qDeleteAll(m_crtcs); qDeleteAll(m_connectors); delete m_cursor[0]; delete m_cursor[1]; close(m_fd); } } void DrmBackend::init() { LogindIntegration *logind = LogindIntegration::self(); auto takeControl = [logind, this]() { if (logind->hasSessionControl()) { openDrm(); } else { logind->takeControl(); connect(logind, &LogindIntegration::hasSessionControlChanged, this, &DrmBackend::openDrm); } }; if (logind->isConnected()) { takeControl(); } else { connect(logind, &LogindIntegration::connectedChanged, this, takeControl); } } void DrmBackend::outputWentOff() { if (!m_dpmsFilter.isNull()) { // already another output is off return; } m_dpmsFilter.reset(new DpmsInputEventFilter(this)); input()->prependInputEventFilter(m_dpmsFilter.data()); } void DrmBackend::turnOutputsOn() { m_dpmsFilter.reset(); for (auto it = m_outputs.constBegin(), end = m_outputs.constEnd(); it != end; it++) { (*it)->setDpms(DrmOutput::DpmsMode::On); } } void DrmBackend::checkOutputsAreOn() { if (m_dpmsFilter.isNull()) { // already disabled, all outputs are on return; } for (auto it = m_outputs.constBegin(), end = m_outputs.constEnd(); it != end; it++) { if (!(*it)->isDpmsEnabled()) { // dpms still disabled, need to keep the filter return; } } // all outputs are on, disable the filter m_dpmsFilter.reset(); } void DrmBackend::activate(bool active) { if (active) { + qCDebug(KWIN_DRM) << "Activating session."; reactivate(); } else { + qCDebug(KWIN_DRM) << "Deactivating session."; deactivate(); } } void DrmBackend::reactivate() { if (m_active) { return; } m_active = true; if (!usesSoftwareCursor()) { DrmDumbBuffer *c = m_cursor[(m_cursorIndex + 1) % 2]; 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); } } // restart compositor m_pageFlipsPending = 0; if (Compositor *compositor = Compositor::self()) { compositor->bufferSwapComplete(); compositor->addRepaintFull(); } } void DrmBackend::deactivate() { if (!m_active) { return; } // block compositor if (m_pageFlipsPending == 0 && Compositor::self()) { Compositor::self()->aboutToSwapBuffers(); } // hide cursor and disable for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { DrmOutput *o = *it; o->hideCursor(); } m_active = false; } void DrmBackend::pageFlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data) { Q_UNUSED(fd) Q_UNUSED(frame) Q_UNUSED(sec) Q_UNUSED(usec) auto output = reinterpret_cast(data); output->pageFlipped(); output->m_backend->m_pageFlipsPending--; 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(); } } } void DrmBackend::openDrm() { connect(LogindIntegration::self(), &LogindIntegration::sessionActiveChanged, this, &DrmBackend::activate); UdevDevice::Ptr device = m_udev->primaryGpu(); if (!device) { qCWarning(KWIN_DRM) << "Did not find a GPU"; return; } int fd = LogindIntegration::self()->takeDevice(device->devNode()); if (fd < 0) { qCWarning(KWIN_DRM) << "failed to open drm device at" << device->devNode(); return; } m_fd = fd; m_active = true; QSocketNotifier *notifier = new QSocketNotifier(m_fd, QSocketNotifier::Read, this); connect(notifier, &QSocketNotifier::activated, this, [this] { if (!LogindIntegration::self()->isActiveSession()) { return; } drmEventContext e; memset(&e, 0, sizeof e); e.version = KWIN_DRM_EVENT_CONTEXT_VERSION; e.page_flip_handler = pageFlipHandler; drmHandleEvent(m_fd, &e); } ); 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); + 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; } } 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."; } } ScopedDrmPointer<_drmModeRes, &drmModeFreeResources> resources(drmModeGetResources(m_fd)); drmModeRes *res = resources.data(); if (!resources) { qCWarning(KWIN_DRM) << "drmModeGetResources failed"; return; } 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"; emit initFailed(); return; } // setup udevMonitor if (m_udevMonitor) { m_udevMonitor->filterSubsystemDevType("drm"); const int fd = m_udevMonitor->fd(); if (fd != -1) { QSocketNotifier *notifier = new QSocketNotifier(fd, QSocketNotifier::Read, this); connect(notifier, &QSocketNotifier::activated, this, [this] { auto device = m_udevMonitor->getDevice(); if (!device) { return; } if (device->sysNum() != m_drmId) { return; } 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(); } } ); m_udevMonitor->enable(); } } setReady(true); initCursor(); } -void DrmBackend::queryResources() +void DrmBackend::updateOutputs() { if (m_fd < 0) { return; } ScopedDrmPointer<_drmModeRes, &drmModeFreeResources> resources(drmModeGetResources(m_fd)); if (!resources) { qCWarning(KWIN_DRM) << "drmModeGetResources failed"; return; } QVector connectedOutputs; QVector pendingConnectors; // split up connected connectors in already or not yet assigned ones for (DrmConnector *con : qAsConst(m_connectors)) { if (!con->isConnected()) { continue; } if (DrmOutput *o = findOutput(con->id())) { connectedOutputs << o; } else { pendingConnectors << con; } } // check for outputs which got removed auto it = m_outputs.begin(); while (it != m_outputs.end()) { if (connectedOutputs.contains(*it)) { it++; continue; } DrmOutput *removed = *it; it = m_outputs.erase(it); emit outputRemoved(removed); delete removed; } // now check new connections for (DrmConnector *con : qAsConst(pendingConnectors)) { ScopedDrmPointer<_drmModeConnector, &drmModeFreeConnector> connector(drmModeGetConnector(m_fd, con->id())); if (!connector) { continue; } if (connector->count_modes == 0) { continue; } bool outputDone = false; QVector encoders = con->encoders(); for (auto encId : qAsConst(encoders)) { ScopedDrmPointer<_drmModeEncoder, &drmModeFreeEncoder> encoder(drmModeGetEncoder(m_fd, encId)); if (!encoder) { continue; } for (DrmCrtc *crtc : qAsConst(m_crtcs)) { if (!(encoder->possible_crtcs & (1 << crtc->resIndex()))) { continue; } // check if crtc isn't used yet -- currently we don't allow multiple outputs on one crtc (cloned mode) auto it = std::find_if(connectedOutputs.constBegin(), connectedOutputs.constEnd(), [crtc] (DrmOutput *o) { return o->m_crtc == crtc; } ); if (it != connectedOutputs.constEnd()) { continue; } // we found a suitable encoder+crtc // TODO: we could avoid these lib drm calls if we store all struct data in DrmCrtc and DrmConnector in the beginning ScopedDrmPointer<_drmModeCrtc, &drmModeFreeCrtc> modeCrtc(drmModeGetCrtc(m_fd, crtc->id())); if (!modeCrtc) { continue; } DrmOutput *output = new DrmOutput(this); con->setOutput(output); output->m_conn = con; crtc->setOutput(output); output->m_crtc = crtc; connect(output, &DrmOutput::dpmsChanged, this, &DrmBackend::outputDpmsChanged); if (modeCrtc->mode_valid) { output->m_mode = modeCrtc->mode; } else { output->m_mode = connector->modes[0]; } qCDebug(KWIN_DRM) << "For new output use mode " << output->m_mode.name; if (!output->init(connector.data())) { qCWarning(KWIN_DRM) << "Failed to create output for connector " << con->id(); con->setOutput(nullptr); crtc->setOutput(nullptr); delete output; continue; } qCDebug(KWIN_DRM) << "Found new output with uuid" << output->uuid(); connectedOutputs << output; emit outputAdded(output); outputDone = true; break; } if (outputDone) { break; } } } std::sort(connectedOutputs.begin(), connectedOutputs.end(), [] (DrmOutput *a, DrmOutput *b) { return a->m_conn->id() < b->m_conn->id(); }); m_outputs = connectedOutputs; readOutputsConfiguration(); if (!m_outputs.isEmpty()) { emit screensQueried(); } } void DrmBackend::readOutputsConfiguration() { if (m_outputs.isEmpty()) { return; } const QByteArray uuid = generateOutputConfigurationUuid(); const auto outputGroup = kwinApp()->config()->group("DrmOutputs"); const auto configGroup = outputGroup.group(uuid); qCDebug(KWIN_DRM) << "Reading output configuration for" << uuid; // default position goes from left to right QPoint pos(0, 0); for (auto it = m_outputs.begin(); it != m_outputs.end(); ++it) { const auto outputConfig = configGroup.group((*it)->uuid()); (*it)->setGlobalPos(outputConfig.readEntry("Position", pos)); // TODO: add mode (*it)->setScale(outputConfig.readEntry("Scale", 1.0)); pos.setX(pos.x() + (*it)->geometry().width()); } } QByteArray DrmBackend::generateOutputConfigurationUuid() const { auto it = m_outputs.constBegin(); if (m_outputs.size() == 1) { // special case: one output return (*it)->uuid(); } QCryptographicHash hash(QCryptographicHash::Md5); for (; it != m_outputs.constEnd(); ++it) { hash.addData((*it)->uuid()); } return hash.result().toHex().left(10); } void DrmBackend::configurationChangeRequested(KWayland::Server::OutputConfigurationInterface *config) { const auto changes = config->changes(); for (auto it = changes.begin(); it != changes.end(); it++) { KWayland::Server::OutputChangeSet *changeset = it.value(); auto drmoutput = findOutput(it.key()->uuid()); if (drmoutput == nullptr) { qCWarning(KWIN_DRM) << "Could NOT find DrmOutput matching " << it.key()->uuid(); return; } drmoutput->setChanges(changeset); } emit screens()->changed(); } DrmOutput *DrmBackend::findOutput(quint32 connector) { auto it = std::find_if(m_outputs.constBegin(), m_outputs.constEnd(), [connector] (DrmOutput *o) { return o->m_conn->id() == connector; }); if (it != m_outputs.constEnd()) { return *it; } return nullptr; } DrmOutput *DrmBackend::findOutput(const QByteArray &uuid) { auto it = std::find_if(m_outputs.constBegin(), m_outputs.constEnd(), [uuid] (DrmOutput *o) { return o->m_uuid == uuid; }); if (it != m_outputs.constEnd()) { return *it; } return nullptr; } void DrmBackend::present(DrmBuffer *buffer, DrmOutput *output) { if (output->present(buffer)) { m_pageFlipsPending++; if (m_pageFlipsPending == 1 && Compositor::self()) { Compositor::self()->aboutToSwapBuffers(); } } } void DrmBackend::initCursor() { m_cursorEnabled = waylandServer()->seat()->hasPointer(); connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::hasPointerChanged, this, [this] { m_cursorEnabled = waylandServer()->seat()->hasPointer(); if (usesSoftwareCursor()) { return; } for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { if (m_cursorEnabled) { (*it)->showCursor(m_cursor[m_cursorIndex]); } else { (*it)->hideCursor(); } } } ); uint64_t capability = 0; QSize cursorSize; if (drmGetCap(m_fd, DRM_CAP_CURSOR_WIDTH, &capability) == 0) { cursorSize.setWidth(capability); } else { cursorSize.setWidth(64); } if (drmGetCap(m_fd, DRM_CAP_CURSOR_HEIGHT, &capability) == 0) { cursorSize.setHeight(capability); } else { cursorSize.setHeight(64); } auto createCursor = [this, cursorSize] (int index) { m_cursor[index] = createBuffer(cursorSize); if (!m_cursor[index]->map(QImage::Format_ARGB32_Premultiplied)) { return false; } m_cursor[index]->image()->fill(Qt::transparent); return true; }; if (!createCursor(0) || !createCursor(1)) { setSoftWareCursor(true); return; } // now we have screens and can set cursors, so start tracking connect(this, &DrmBackend::cursorChanged, this, &DrmBackend::updateCursor); connect(Cursor::self(), &Cursor::posChanged, this, &DrmBackend::moveCursor); } void DrmBackend::setCursor() { DrmDumbBuffer *c = m_cursor[m_cursorIndex]; m_cursorIndex = (m_cursorIndex + 1) % 2; if (m_cursorEnabled) { for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { (*it)->showCursor(c); } } markCursorAsRendered(); } void DrmBackend::updateCursor() { if (usesSoftwareCursor()) { return; } if (isCursorHidden()) { return; } const QImage &cursorImage = softwareCursor(); if (cursorImage.isNull()) { doHideCursor(); return; } QImage *c = m_cursor[m_cursorIndex]->image(); c->fill(Qt::transparent); QPainter p; p.begin(c); p.drawImage(QPoint(0, 0), cursorImage); p.end(); setCursor(); moveCursor(); } void DrmBackend::doShowCursor() { updateCursor(); } void DrmBackend::doHideCursor() { if (!m_cursorEnabled) { return; } for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { (*it)->hideCursor(); } } void DrmBackend::moveCursor() { const QPoint p = Cursor::pos() - softwareCursorHotspot(); if (!m_cursorEnabled || isCursorHidden()) { return; } for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { (*it)->moveCursor(p); } } Screens *DrmBackend::createScreens(QObject *parent) { return new DrmScreens(this, parent); } QPainterBackend *DrmBackend::createQPainterBackend() { m_deleteBufferAfterPageFlip = false; return new DrmQPainterBackend(this); } OpenGLBackend *DrmBackend::createOpenGLBackend() { #if HAVE_GBM m_deleteBufferAfterPageFlip = true; return new EglGbmBackend(this); #else return Platform::createOpenGLBackend(); #endif } DrmDumbBuffer *DrmBackend::createBuffer(const QSize &size) { DrmDumbBuffer *b = new DrmDumbBuffer(this, size); return b; } DrmSurfaceBuffer *DrmBackend::createBuffer(gbm_surface *surface) { #if HAVE_GBM DrmSurfaceBuffer *b = new DrmSurfaceBuffer(this, surface); return b; #else return nullptr; #endif } void DrmBackend::outputDpmsChanged() { if (m_outputs.isEmpty()) { return; } bool enabled = false; for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { enabled = enabled || (*it)->isDpmsEnabled(); } setOutputsEnabled(enabled); } - } diff --git a/plugins/platforms/drm/drm_backend.h b/plugins/platforms/drm/drm_backend.h index 75dbbae2f..c1d2ec095 100644 --- a/plugins/platforms/drm/drm_backend.h +++ b/plugins/platforms/drm/drm_backend.h @@ -1,169 +1,173 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin 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_BACKEND_H #define KWIN_DRM_BACKEND_H #include "platform.h" #include "input.h" #include "drm_buffer.h" #if HAVE_GBM #include "drm_buffer_gbm.h" #endif #include "drm_inputeventfilter.h" #include "drm_pointer.h" #include #include #include #include #include struct gbm_bo; struct gbm_device; struct gbm_surface; namespace KWayland { namespace Server { class OutputInterface; class OutputDeviceInterface; class OutputChangeSet; class OutputManagementInterface; } } namespace KWin { class Udev; class UdevMonitor; class DrmOutput; class DrmPlane; class DrmCrtc; class DrmConnector; class KWIN_EXPORT DrmBackend : public Platform { Q_OBJECT Q_INTERFACES(KWin::Platform) Q_PLUGIN_METADATA(IID "org.kde.kwin.Platform" FILE "drm.json") public: explicit DrmBackend(QObject *parent = nullptr); virtual ~DrmBackend(); void configurationChangeRequested(KWayland::Server::OutputConfigurationInterface *config) override; Screens *createScreens(QObject *parent = nullptr) override; QPainterBackend *createQPainterBackend() override; OpenGLBackend* createOpenGLBackend() override; void init() override; DrmDumbBuffer *createBuffer(const QSize &size); DrmSurfaceBuffer *createBuffer(gbm_surface *surface); void present(DrmBuffer *buffer, DrmOutput *output); int fd() const { return m_fd; } QVector outputs() const { return m_outputs; } QVector planes() const { return m_planes; } + QVector overlayPlanes() const { + return m_overlayPlanes; + } void outputWentOff(); void checkOutputsAreOn(); // QPainter reuses buffers bool deleteBufferAfterPageFlip() const { return m_deleteBufferAfterPageFlip; } // returns use of AMS, default is not/legacy bool atomicModeSetting() const { return m_atomicModeSetting; } void setGbmDevice(gbm_device *device) { m_gbmDevice = device; } gbm_device *gbmDevice() const { return m_gbmDevice; } public Q_SLOTS: void turnOutputsOn(); Q_SIGNALS: void outputRemoved(KWin::DrmOutput *output); void outputAdded(KWin::DrmOutput *output); protected: void doHideCursor() override; void doShowCursor() override; private: static void pageFlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data); void openDrm(); void activate(bool active); void reactivate(); void deactivate(); - void queryResources(); + void updateOutputs(); void setCursor(); void updateCursor(); void moveCursor(); void initCursor(); void outputDpmsChanged(); void readOutputsConfiguration(); QByteArray generateOutputConfigurationUuid() const; DrmOutput *findOutput(quint32 connector); DrmOutput *findOutput(const QByteArray &uuid); QScopedPointer m_udev; QScopedPointer m_udevMonitor; int m_fd = -1; int m_drmId = 0; // all crtcs QVector m_crtcs; // all connectors QVector m_connectors; // currently active output pipelines (planes + crtc + encoder + connector) QVector m_outputs; DrmDumbBuffer *m_cursor[2]; bool m_deleteBufferAfterPageFlip; bool m_atomicModeSetting = false; bool m_cursorEnabled = false; int m_cursorIndex = 0; int m_pageFlipsPending = 0; bool m_active = false; // all available planes: primarys, cursors and overlays QVector m_planes; + QVector m_overlayPlanes; QScopedPointer m_dpmsFilter; KWayland::Server::OutputManagementInterface *m_outputManagement = nullptr; gbm_device *m_gbmDevice = nullptr; }; } #endif diff --git a/plugins/platforms/drm/drm_object.cpp b/plugins/platforms/drm/drm_object.cpp index d3a438d29..579b06b83 100644 --- a/plugins/platforms/drm/drm_object.cpp +++ b/plugins/platforms/drm/drm_object.cpp @@ -1,145 +1,147 @@ /******************************************************************** 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 "drm_backend.h" #include "logging.h" 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) { } 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]); + drmModePropertyRes *prop = drmModeGetProperty(m_backend->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) +bool DrmObject::atomicAddProperty(drmModeAtomicReq *req, int prop, uint64_t value) { - Q_ASSERT(index < m_props.size()); - m_props[index]->setValue(new_value); - return; + 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::atomicAddProperty(drmModeAtomicReq *req, int prop, uint64_t value) +bool DrmObject::atomicPopulate(drmModeAtomicReq *req) { - 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; + bool ret = true; + + for (int i = 0; i < m_props.size(); i++) { + ret &= atomicAddProperty(req, i, m_props[i]->value()); } - if (drmModeAtomicAddProperty(req, m_id, m_props[prop]->propId(), value) < 0) { - // error when adding property + + 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; } /* * 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.h b/plugins/platforms/drm/drm_object.h index 168c9c092..6c46a6bcf 100644 --- a/plugins/platforms/drm/drm_object.h +++ b/plugins/platforms/drm/drm_object.h @@ -1,137 +1,122 @@ /******************************************************************** 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 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(); + uint64_t value(int prop) { + return m_props[prop]->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; + void setValue(int prop, uint64_t new_value) + { + Q_ASSERT(prop < m_props.size()); + m_props[prop]->setValue(new_value); } - bool atomicAddProperty(drmModeAtomicReq *req, int prop, uint64_t value); + virtual bool atomicPopulate(drmModeAtomicReq *req); protected: - const int m_fd = 0; - const uint32_t m_id = 0; + virtual bool initProps() = 0; // only derived classes know names and quantity of properties + void initProp(int n, drmModeObjectProperties *properties, QVector enumNames = QVector(0)); + bool atomicAddProperty(drmModeAtomicReq *req, int prop, uint64_t value); + DrmBackend *m_backend; + const uint32_t m_id = 0; DrmOutput *m_output = nullptr; - QVector m_propsNames; // for comparision with received name of DRM object + // for comparision with received name of DRM object + QVector m_propsNames; 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)); + QVector m_props; 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() { + uint64_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_connector.cpp b/plugins/platforms/drm/drm_object_connector.cpp index 4f5552e57..444ce4eef 100644 --- a/plugins/platforms/drm/drm_object_connector.cpp +++ b/plugins/platforms/drm/drm_object_connector.cpp @@ -1,80 +1,81 @@ /******************************************************************** 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 "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; } for (int i = 0; i < con->count_encoders; ++i) { m_encoders << con->encoders[i]; } } DrmConnector::~DrmConnector() = default; -bool DrmConnector::init() +bool DrmConnector::atomicInit() { 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); + 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; } int propCount = int(PropertyIndex::Count); for (int j = 0; j < propCount; ++j) { initProp(j, properties); } drmModeFreeObjectProperties(properties); return true; } 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; } return con->connection == DRM_MODE_CONNECTED; } } diff --git a/plugins/platforms/drm/drm_object_connector.h b/plugins/platforms/drm/drm_object_connector.h index 6257febb8..0f6fcdbd4 100644 --- a/plugins/platforms/drm/drm_object_connector.h +++ b/plugins/platforms/drm/drm_object_connector.h @@ -1,57 +1,57 @@ /******************************************************************** 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); + DrmConnector(uint32_t connector_id, DrmBackend *backend); virtual ~DrmConnector(); - bool init(); + bool atomicInit(); enum class PropertyIndex { CrtcId = 0, Count }; QVector encoders() { return m_encoders; } bool initProps(); bool isConnected(); private: QVector m_encoders; }; } #endif diff --git a/plugins/platforms/drm/drm_object_crtc.cpp b/plugins/platforms/drm/drm_object_crtc.cpp index 3aa3870cd..20b232ef8 100644 --- a/plugins/platforms/drm/drm_object_crtc.cpp +++ b/plugins/platforms/drm/drm_object_crtc.cpp @@ -1,107 +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 . *********************************************************************/ #include "drm_object_crtc.h" #include "drm_backend.h" #include "drm_output.h" #include "drm_buffer.h" #include "logging.h" 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) { } DrmCrtc::~DrmCrtc() { } -bool DrmCrtc::init() +bool DrmCrtc::atomicInit() { qCDebug(KWIN_DRM) << "Atomic init for CRTC:" << resIndex() << "id:" << 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); + 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; } int propCount = int(PropertyIndex::Count); for (int j = 0; j < propCount; ++j) { initProp(j, properties); } drmModeFreeObjectProperties(properties); return true; } 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; m_nextBuffer = nullptr; delete m_blackBuffer; m_blackBuffer = nullptr; } 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; } blackBuffer->image()->fill(Qt::black); 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; } m_currentBuffer = nullptr; m_nextBuffer = nullptr; return true; } return false; } } diff --git a/plugins/platforms/drm/drm_object_crtc.h b/plugins/platforms/drm/drm_object_crtc.h index 789c08e4a..c5fc37d21 100644 --- a/plugins/platforms/drm/drm_object_crtc.h +++ b/plugins/platforms/drm/drm_object_crtc.h @@ -1,78 +1,77 @@ /******************************************************************** 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 DrmBackend; class DrmBuffer; 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, Active, Count }; bool initProps(); int resIndex() const { return m_resIndex; } DrmBuffer *current() { return m_currentBuffer; } DrmBuffer *next() { return m_nextBuffer; } void setNext(DrmBuffer *buffer) { m_nextBuffer = buffer; } void flipBuffer(); bool blank(); private: - DrmBackend *m_backend; int m_resIndex; DrmBuffer *m_currentBuffer = nullptr; DrmBuffer *m_nextBuffer = nullptr; DrmDumbBuffer *m_blackBuffer = nullptr; }; } #endif diff --git a/plugins/platforms/drm/drm_object_plane.cpp b/plugins/platforms/drm/drm_object_plane.cpp index 85b4b4447..8c899a5b0 100644 --- a/plugins/platforms/drm/drm_object_plane.cpp +++ b/plugins/platforms/drm/drm_object_plane.cpp @@ -1,174 +1,151 @@ /******************************************************************** 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_backend.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(uint32_t plane_id, DrmBackend *backend) + : DrmObject(plane_id, backend) { } -DrmPlane::~DrmPlane() = default; +DrmPlane::~DrmPlane() +{ + delete m_current; + delete m_next; +} -bool DrmPlane::init() +bool DrmPlane::atomicInit() { - qCDebug(KWIN_DRM) << "Initialize plane" << m_id; - ScopedDrmPointer<_drmModePlane, &drmModeFreePlane> p(drmModeGetPlane(m_fd, m_id)); + 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; return false; } 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]; } 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); + 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; } 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)); + 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::setFormats(uint32_t const *f, int fcount) -{ - m_formats.resize(fcount); - for (int i = 0; i < fcount; i++) { - m_formats[i] = *f; - } +void DrmPlane::setNext(DrmBuffer *b){ + setValue(int(PropertyIndex::FbId), b ? b->bufferId() : 0); + m_next = b; } -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(); } } diff --git a/plugins/platforms/drm/drm_object_plane.h b/plugins/platforms/drm/drm_object_plane.h index bef27ce9f..280546204 100644 --- a/plugins/platforms/drm/drm_object_plane.h +++ b/plugins/platforms/drm/drm_object_plane.h @@ -1,106 +1,101 @@ /******************************************************************** 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); + DrmPlane(uint32_t plane_id, DrmBackend *backend); - virtual ~DrmPlane(); + ~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 atomicInit(); bool initProps(); TypeIndex type(); - bool isCrtcSupported(uint32_t crtc); - DrmObject::AtomicReturn atomicReqPlanePopulate(drmModeAtomicReq *req); - DrmBuffer *current(){ + bool isCrtcSupported(int resIndex) const { + return (m_possibleCrtcs & (1 << resIndex)); + } + QVector formats() const { + return m_formats; + } + + DrmBuffer *current() const { return m_current; } - DrmBuffer *next(){ + DrmBuffer *next() const { return m_next; } - void setCurrent(DrmBuffer *b){ + void setCurrent(DrmBuffer *b) { m_current = b; } - void setNext(DrmBuffer *b){ - m_next = b; - } + void setNext(DrmBuffer *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; - } + bool atomicPopulate(drmModeAtomicReq *req); + void flipBuffer(); + void flipBufferWithDelete(); 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_output.cpp b/plugins/platforms/drm/drm_output.cpp index cd6d027bb..334af4363 100644 --- a/plugins/platforms/drm/drm_output.cpp +++ b/plugins/platforms/drm/drm_output.cpp @@ -1,905 +1,1006 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin 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_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" #include "logging.h" #include "main.h" #include "screens_drm.h" #include "wayland_server.h" // KWayland #include #include #include #include #include #include // KF5 #include #include #include // Qt #include // drm #include #include #include namespace KWin { DrmOutput::DrmOutput(DrmBackend *backend) : QObject() , m_backend(backend) { } 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(); } 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(); } } void DrmOutput::hideCursor() { drmModeSetCursor(m_backend->fd(), m_crtc->id(), 0, 0, 0); } void DrmOutput::showCursor(DrmDumbBuffer *c) { const QSize &s = c->size(); drmModeSetCursor(m_backend->fd(), m_crtc->id(), c->handle(), s.width(), s.height()); } void DrmOutput::moveCursor(const QPoint &globalPos) { const QPoint p = (globalPos - m_globalPos) * m_scale; drmModeMoveCursor(m_backend->fd(), m_crtc->id(), p.x(), p.y()); } QSize DrmOutput::pixelSize() const { return QSize(m_mode.hdisplay, m_mode.vdisplay); } QRect DrmOutput::geometry() const { return QRect(m_globalPos, pixelSize() / scale()); } qreal DrmOutput::scale() const { return m_scale; } static KWayland::Server::OutputInterface::DpmsMode toWaylandDpmsMode(DrmOutput::DpmsMode mode) { using namespace KWayland::Server; switch (mode) { case DrmOutput::DpmsMode::On: return OutputInterface::DpmsMode::On; case DrmOutput::DpmsMode::Standby: return OutputInterface::DpmsMode::Standby; case DrmOutput::DpmsMode::Suspend: return OutputInterface::DpmsMode::Suspend; case DrmOutput::DpmsMode::Off: return OutputInterface::DpmsMode::Off; default: Q_UNREACHABLE(); } } static DrmOutput::DpmsMode fromWaylandDpmsMode(KWayland::Server::OutputInterface::DpmsMode wlMode) { using namespace KWayland::Server; switch (wlMode) { case OutputInterface::DpmsMode::On: return DrmOutput::DpmsMode::On; case OutputInterface::DpmsMode::Standby: return DrmOutput::DpmsMode::Standby; case OutputInterface::DpmsMode::Suspend: return DrmOutput::DpmsMode::Suspend; case OutputInterface::DpmsMode::Off: return DrmOutput::DpmsMode::Off; default: Q_UNREACHABLE(); } } static QHash s_connectorNames = { {DRM_MODE_CONNECTOR_Unknown, QByteArrayLiteral("Unknown")}, {DRM_MODE_CONNECTOR_VGA, QByteArrayLiteral("VGA")}, {DRM_MODE_CONNECTOR_DVII, QByteArrayLiteral("DVI-I")}, {DRM_MODE_CONNECTOR_DVID, QByteArrayLiteral("DVI-D")}, {DRM_MODE_CONNECTOR_DVIA, QByteArrayLiteral("DVI-A")}, {DRM_MODE_CONNECTOR_Composite, QByteArrayLiteral("Composite")}, {DRM_MODE_CONNECTOR_SVIDEO, QByteArrayLiteral("SVIDEO")}, {DRM_MODE_CONNECTOR_LVDS, QByteArrayLiteral("LVDS")}, {DRM_MODE_CONNECTOR_Component, QByteArrayLiteral("Component")}, {DRM_MODE_CONNECTOR_9PinDIN, QByteArrayLiteral("DIN")}, {DRM_MODE_CONNECTOR_DisplayPort, QByteArrayLiteral("DP")}, {DRM_MODE_CONNECTOR_HDMIA, QByteArrayLiteral("HDMI-A")}, {DRM_MODE_CONNECTOR_HDMIB, QByteArrayLiteral("HDMI-B")}, {DRM_MODE_CONNECTOR_TV, QByteArrayLiteral("TV")}, {DRM_MODE_CONNECTOR_eDP, QByteArrayLiteral("eDP")}, {DRM_MODE_CONNECTOR_VIRTUAL, QByteArrayLiteral("Virtual")}, {DRM_MODE_CONNECTOR_DSI, QByteArrayLiteral("DSI")} }; bool DrmOutput::init(drmModeConnector *connector) { initEdid(connector); initDpms(connector); initUuid(); if (m_backend->atomicModeSetting()) { 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(); m_waylandOutput.clear(); } m_waylandOutput = waylandServer()->display()->createOutput(); if (!m_waylandOutputDevice.isNull()) { delete m_waylandOutputDevice.data(); m_waylandOutputDevice.clear(); } m_waylandOutputDevice = waylandServer()->display()->createOutputDevice(); m_waylandOutputDevice->setUuid(m_uuid); if (!m_edid.eisaId.isEmpty()) { m_waylandOutput->setManufacturer(QString::fromLatin1(m_edid.eisaId)); } else { m_waylandOutput->setManufacturer(i18n("unknown")); } m_waylandOutputDevice->setManufacturer(m_waylandOutput->manufacturer()); QString connectorName = s_connectorNames.value(connector->connector_type, QByteArrayLiteral("Unknown")); QString modelName; if (!m_edid.monitorName.isEmpty()) { QString model = QString::fromLatin1(m_edid.monitorName); if (!m_edid.serialNumber.isEmpty()) { model.append('/'); model.append(QString::fromLatin1(m_edid.serialNumber)); } modelName = model; } else if (!m_edid.serialNumber.isEmpty()) { modelName = QString::fromLatin1(m_edid.serialNumber); } else { modelName = i18n("unknown"); } m_waylandOutput->setModel(connectorName + QStringLiteral("-") + QString::number(connector->connector_type_id) + QStringLiteral("-") + modelName); m_waylandOutputDevice->setModel(m_waylandOutput->model()); QSize physicalSize = !m_edid.physicalSize.isEmpty() ? m_edid.physicalSize : QSize(connector->mmWidth, connector->mmHeight); // the size might be completely borked. E.g. Samsung SyncMaster 2494HS reports 160x90 while in truth it's 520x292 // as this information is used to calculate DPI info, it's going to result in everything being huge const QByteArray unknown = QByteArrayLiteral("unkown"); KConfigGroup group = kwinApp()->config()->group("EdidOverwrite").group(m_edid.eisaId.isEmpty() ? unknown : m_edid.eisaId) .group(m_edid.monitorName.isEmpty() ? unknown : m_edid.monitorName) .group(m_edid.serialNumber.isEmpty() ? unknown : m_edid.serialNumber); if (group.hasKey("PhysicalSize")) { const QSize overwriteSize = group.readEntry("PhysicalSize", physicalSize); qCWarning(KWIN_DRM) << "Overwriting monitor physical size for" << m_edid.eisaId << "/" << m_edid.monitorName << "/" << m_edid.serialNumber << " from " << physicalSize << "to " << overwriteSize; physicalSize = overwriteSize; } m_waylandOutput->setPhysicalSize(physicalSize); m_waylandOutputDevice->setPhysicalSize(physicalSize); // 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; if (isCurrentMode(m)) { flags |= KWayland::Server::OutputInterface::ModeFlag::Current; deviceflags |= KWayland::Server::OutputDeviceInterface::ModeFlag::Current; } if (m->type & DRM_MODE_TYPE_PREFERRED) { flags |= KWayland::Server::OutputInterface::ModeFlag::Preferred; deviceflags |= KWayland::Server::OutputDeviceInterface::ModeFlag::Preferred; } // Calculate higher precision (mHz) refresh rate // logic based on Weston, see compositor-drm.c quint64 refreshRate = (m->clock * 1000000LL / m->htotal + m->vtotal / 2) / m->vtotal; if (m->flags & DRM_MODE_FLAG_INTERLACE) { refreshRate *= 2; } if (m->flags & DRM_MODE_FLAG_DBLSCAN) { refreshRate /= 2; } if (m->vscan > 1) { refreshRate /= m->vscan; } m_waylandOutput->addMode(QSize(m->hdisplay, m->vdisplay), flags, refreshRate); KWayland::Server::OutputDeviceInterface::Mode mode; mode.id = i; mode.size = QSize(m->hdisplay, m->vdisplay); mode.flags = deviceflags; mode.refreshRate = refreshRate; qCDebug(KWIN_DRM) << "Adding mode: " << i << mode.size; m_waylandOutputDevice->addMode(mode); } // set dpms if (!m_dpms.isNull()) { m_waylandOutput->setDpmsSupported(true); m_waylandOutput->setDpmsMode(toWaylandDpmsMode(m_dpmsMode)); connect(m_waylandOutput.data(), &KWayland::Server::OutputInterface::dpmsModeRequested, this, [this] (KWayland::Server::OutputInterface::DpmsMode mode) { setDpms(fromWaylandDpmsMode(mode)); }, Qt::QueuedConnection ); } m_waylandOutput->create(); qCDebug(KWIN_DRM) << "Created OutputDevice"; m_waylandOutputDevice->create(); return true; } void DrmOutput::initUuid() { QCryptographicHash hash(QCryptographicHash::Md5); hash.addData(QByteArray::number(m_conn->id())); hash.addData(m_edid.eisaId); hash.addData(m_edid.monitorName); hash.addData(m_edid.serialNumber); m_uuid = hash.result().toHex().left(10); } bool DrmOutput::isCurrentMode(const drmModeModeInfo *mode) const { return mode->clock == m_mode.clock && mode->hdisplay == m_mode.hdisplay && mode->hsync_start == m_mode.hsync_start && mode->hsync_end == m_mode.hsync_end && mode->htotal == m_mode.htotal && mode->hskew == m_mode.hskew && mode->vdisplay == m_mode.vdisplay && mode->vsync_start == m_mode.vsync_start && mode->vsync_end == m_mode.vsync_end && mode->vtotal == m_mode.vtotal && mode->vscan == m_mode.vscan && mode->vrefresh == m_mode.vrefresh && mode->flags == m_mode.flags && mode->type == m_mode.type && qstrcmp(mode->name, m_mode.name) == 0; } static bool verifyEdidHeader(drmModePropertyBlobPtr edid) { const uint8_t *data = reinterpret_cast(edid->data); if (data[0] != 0x00) { return false; } for (int i = 1; i < 7; ++i) { if (data[i] != 0xFF) { return false; } } if (data[7] != 0x00) { return false; } return true; } static QByteArray extractEisaId(drmModePropertyBlobPtr edid) { /* * From EDID standard section 3.4: * The ID Manufacturer Name field, shown in Table 3.5, contains a 2-byte representation of the monitor's * manufacturer. This is the same as the EISA ID. It is based on compressed ASCII, “0001=A” ... “11010=Z”. * * The table: * | Byte | Bit | * | | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | * ---------------------------------------- * | 1 | 0)| (4| 3 | 2 | 1 | 0)| (4| 3 | * | | * | Character 1 | Char 2| * ---------------------------------------- * | 2 | 2 | 1 | 0)| (4| 3 | 2 | 1 | 0)| * | | Character2| Character 3 | * ---------------------------------------- **/ const uint8_t *data = reinterpret_cast(edid->data); static const uint offset = 0x8; char id[4]; if (data[offset] >> 7) { // bit at position 7 is not a 0 return QByteArray(); } // shift two bits to right, and with 7 right most bits id[0] = 'A' + ((data[offset] >> 2) & 0x1f) -1; // for first byte: take last two bits and shift them 3 to left (000xx000) // for second byte: shift 5 bits to right and take 3 right most bits (00000xxx) // or both together id[1] = 'A' + (((data[offset] & 0x3) << 3) | ((data[offset + 1] >> 5) & 0x7)) - 1; // take five right most bits id[2] = 'A' + (data[offset + 1] & 0x1f) - 1; id[3] = '\0'; return QByteArray(id); } static void extractMonitorDescriptorDescription(drmModePropertyBlobPtr blob, DrmOutput::Edid &edid) { // see section 3.10.3 const uint8_t *data = reinterpret_cast(blob->data); static const uint offset = 0x36; static const uint blockLength = 18; for (int i = 0; i < 5; ++i) { const uint co = offset + i * blockLength; // Flag = 0000h when block used as descriptor if (data[co] != 0) { continue; } if (data[co + 1] != 0) { continue; } // Reserved = 00h when block used as descriptor if (data[co + 2] != 0) { continue; } /* * FFh: Monitor Serial Number - Stored as ASCII, code page # 437, ≤ 13 bytes. * FEh: ASCII String - Stored as ASCII, code page # 437, ≤ 13 bytes. * FDh: Monitor range limits, binary coded * FCh: Monitor name, stored as ASCII, code page # 437 * FBh: Descriptor contains additional color point data * FAh: Descriptor contains additional Standard Timing Identifications * F9h - 11h: Currently undefined * 10h: Dummy descriptor, used to indicate that the descriptor space is unused * 0Fh - 00h: Descriptor defined by manufacturer. */ if (data[co + 3] == 0xfc && edid.monitorName.isEmpty()) { edid.monitorName = QByteArray((const char *)(&data[co + 5]), 12).trimmed(); } if (data[co + 3] == 0xfe) { const QByteArray id = QByteArray((const char *)(&data[co + 5]), 12).trimmed(); if (!id.isEmpty()) { edid.eisaId = id; } } if (data[co + 3] == 0xff) { edid.serialNumber = QByteArray((const char *)(&data[co + 5]), 12).trimmed(); } } } static QByteArray extractSerialNumber(drmModePropertyBlobPtr edid) { // see section 3.4 const uint8_t *data = reinterpret_cast(edid->data); static const uint offset = 0x0C; /* * The ID serial number is a 32-bit serial number used to differentiate between individual instances of the same model * of monitor. Its use is optional. When used, the bit order for this field follows that shown in Table 3.6. The EDID * structure Version 1 Revision 1 and later offer a way to represent the serial number of the monitor as an ASCII string * in a separate descriptor block. */ uint32_t serialNumber = 0; serialNumber = (uint32_t) data[offset + 0]; serialNumber |= (uint32_t) data[offset + 1] << 8; serialNumber |= (uint32_t) data[offset + 2] << 16; serialNumber |= (uint32_t) data[offset + 3] << 24; if (serialNumber == 0) { return QByteArray(); } return QByteArray::number(serialNumber); } static QSize extractPhysicalSize(drmModePropertyBlobPtr edid) { const uint8_t *data = reinterpret_cast(edid->data); return QSize(data[0x15], data[0x16]) * 10; } void DrmOutput::initEdid(drmModeConnector *connector) { ScopedDrmPointer<_drmModePropertyBlob, &drmModeFreePropertyBlob> edid; for (int i = 0; i < connector->count_props; ++i) { ScopedDrmPointer<_drmModeProperty, &drmModeFreeProperty> property(drmModeGetProperty(m_backend->fd(), connector->props[i])); if (!property) { continue; } if ((property->flags & DRM_MODE_PROP_BLOB) && qstrcmp(property->name, "EDID") == 0) { edid.reset(drmModeGetPropertyBlob(m_backend->fd(), connector->prop_values[i])); } } if (!edid) { return; } // for documentation see: http://read.pudn.com/downloads110/ebook/456020/E-EDID%20Standard.pdf if (edid->length < 128) { return; } if (!verifyEdidHeader(edid.data())) { return; } m_edid.eisaId = extractEisaId(edid.data()); m_edid.serialNumber = extractSerialNumber(edid.data()); // parse monitor descriptor description extractMonitorDescriptorDescription(edid.data(), m_edid); 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_crtc->id())) { + if (!p->isCrtcSupported(m_crtc->resIndex())) { continue; } p->setOutput(this); m_primaryPlane = p; qCDebug(KWIN_DRM) << "Initialized primary plane" << p->id() << "on CRTC" << m_crtc->id(); 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_crtc->id())) { + if (!p->isCrtcSupported(m_crtc->resIndex())) { continue; } p->setOutput(this); m_cursorPlane = p; qCDebug(KWIN_DRM) << "Initialized cursor plane" << p->id() << "on CRTC" << m_crtc->id(); return true; } return false; } void DrmOutput::initDpms(drmModeConnector *connector) { for (int i = 0; i < connector->count_props; ++i) { ScopedDrmPointer<_drmModeProperty, &drmModeFreeProperty> property(drmModeGetProperty(m_backend->fd(), connector->props[i])); if (!property) { continue; } if (qstrcmp(property->name, "DPMS") == 0) { m_dpms.swap(property); break; } } } 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 { if (!m_waylandOutput) { return i18n("unknown"); } return QStringLiteral("%1 %2").arg(m_waylandOutput->manufacturer()).arg(m_waylandOutput->model()); } int DrmOutput::currentRefreshRate() const { if (!m_waylandOutput) { return 60000; } return m_waylandOutput->refreshRate(); } void DrmOutput::setGlobalPos(const QPoint &pos) { m_globalPos = pos; if (m_waylandOutput) { m_waylandOutput->setGlobalPosition(pos); } if (m_waylandOutputDevice) { m_waylandOutputDevice->setGlobalPosition(pos); } } void DrmOutput::setScale(qreal scale) { m_scale = scale; if (m_waylandOutput) { m_waylandOutput->setScale(scale); } if (m_waylandOutputDevice) { m_waylandOutputDevice->setScale(scale); } } void DrmOutput::setChanges(KWayland::Server::OutputChangeSet *changes) { m_changeset = changes; qCDebug(KWIN_DRM) << "set changes in DrmOutput"; commitChanges(); } bool DrmOutput::commitChanges() { Q_ASSERT(!m_waylandOutputDevice.isNull()); Q_ASSERT(!m_waylandOutput.isNull()); if (m_changeset.isNull()) { qCDebug(KWIN_DRM) << "no changes"; // No changes to an output is an entirely valid thing return true; } if (m_changeset->enabledChanged()) { qCDebug(KWIN_DRM) << "Setting enabled:"; m_waylandOutputDevice->setEnabled(m_changeset->enabled()); } if (m_changeset->modeChanged()) { qCDebug(KWIN_DRM) << "Setting new mode:" << m_changeset->mode(); m_waylandOutputDevice->setCurrentMode(m_changeset->mode()); // FIXME: implement for wl_output } if (m_changeset->transformChanged()) { qCDebug(KWIN_DRM) << "Server setting transform: " << (int)(m_changeset->transform()); m_waylandOutputDevice->setTransform(m_changeset->transform()); // FIXME: implement for wl_output } if (m_changeset->positionChanged()) { qCDebug(KWIN_DRM) << "Server setting position: " << m_changeset->position(); m_waylandOutput->setGlobalPosition(m_changeset->position()); m_waylandOutputDevice->setGlobalPosition(m_changeset->position()); setGlobalPos(m_changeset->position()); // may just work already! } if (m_changeset->scaleChanged()) { qCDebug(KWIN_DRM) << "Setting scale:" << m_changeset->scale(); setScale(m_changeset->scale()); } return true; } 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); + // 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; + } + 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(); } - m_planesFlipList.clear(); - } else { - if (!m_crtc->next()) { - // on manual vt switch - if (DrmBuffer *b = m_crtc->current()) { - b->releaseGbm(); + if (m_backend->atomicModeSetting()){ + for (DrmPlane *p : m_nextPlanesFlipList) { + p->flipBuffer(); } - return; + 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) { return false; } if (m_backend->atomicModeSetting()) { return presentAtomically(buffer); } else { return presentLegacy(buffer); } } -bool DrmOutput::presentAtomically(DrmBuffer *buffer) +bool DrmOutput::dpmsAtomicOff() { - 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."; + 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 (m_primaryPlane->next()) { - qCWarning(KWIN_DRM) << "Page not yet flipped."; + 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(); - 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"; + return true; - 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; - } +bool DrmOutput::presentAtomically(DrmBuffer *buffer) +{ + if (!LogindIntegration::self()->isActiveSession()) { + qCWarning(KWIN_DRM) << "Logind session not active."; + return false; } - 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; - } + if (m_pageFlipPending) { + qCWarning(KWIN_DRM) << "Page not yet flipped."; + return false; } - // 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(); + m_primaryPlane->setNext(buffer); + m_nextPlanesFlipList << m_primaryPlane; + + if (!doAtomicCommit(AtomicCommitMode::Test)) { + //TODO: When we use planes for layered rendering, fallback to renderer instead. Also for direct scanout? + qCDebug(KWIN_DRM) << "Atomic test commit failed. Aborting present."; + if (this->m_backend->deleteBufferAfterPageFlip()) { 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()); + if (!doAtomicCommit(AtomicCommitMode::Real)) { + qCDebug(KWIN_DRM) << "Atomic commit failed. This should have never happened! Aborting present."; + return false; } - - drmModeAtomicFree(req); + m_pageFlipPending = true; return true; } - bool DrmOutput::presentLegacy(DrmBuffer *buffer) { if (m_crtc->next()) { return false; } if (!LogindIntegration::self()->isActiveSession()) { m_crtc->setNext(buffer); return false; } if (m_dpmsMode != DpmsMode::On) { return false; } // Do we need to set a new mode first? if (!m_crtc->current() || m_crtc->current()->needsModeChange(buffer)) { if (!setModeLegacy(buffer)) { return false; } } int errno_save = 0; const bool ok = drmModePageFlip(m_backend->fd(), m_crtc->id(), buffer->bufferId(), DRM_MODE_PAGE_FLIP_EVENT, this) == 0; if (ok) { m_crtc->setNext(buffer); } else { errno_save = errno; qCWarning(KWIN_DRM) << "Page flip failed:" << strerror(errno); delete buffer; } return ok; } bool DrmOutput::setModeLegacy(DrmBuffer *buffer) { uint32_t connId = m_conn->id(); if (drmModeSetCrtc(m_backend->fd(), m_crtc->id(), buffer->bufferId(), 0, 0, &connId, 1, &m_mode) == 0) { return true; } else { qCWarning(KWIN_DRM) << "Mode setting failed"; return false; } } -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; + // 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); + } - m_crtc->setPropsPending(0); - m_conn->setPropsPending(0); + if (!ret) { + qCWarning(KWIN_DRM) << "Failed to populate atomic planes. Abort atomic commit!"; + errorHandler(); + return false; + } - 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 (drmModeAtomicCommit(m_backend->fd(), req, flags, this)) { + qCWarning(KWIN_DRM) << "Atomic request failed to commit:" << strerror(errno); + errorHandler(); + return false; + } - if (!ret) { - qCWarning(KWIN_DRM) << "Failed to populate atomic modeset"; - return DrmObject::AtomicReturn::Error; + if (mode == AtomicCommitMode::Real && (flags & DRM_MODE_ATOMIC_ALLOW_MODESET)) { + qCDebug(KWIN_DRM) << "Atomic Modeset successful."; + m_modesetRequested = false; + m_dpmsMode = m_dpmsModePending; } - if (!m_crtc->propsPending() && !m_conn->propsPending()) { - return DrmObject::AtomicReturn::NoChange; + + 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); } - return DrmObject::AtomicReturn::Success; + 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; } } diff --git a/plugins/platforms/drm/drm_output.h b/plugins/platforms/drm/drm_output.h index f262f016b..6f358de1b 100644 --- a/plugins/platforms/drm/drm_output.h +++ b/plugins/platforms/drm/drm_output.h @@ -1,154 +1,169 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin 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_OUTPUT_H #define KWIN_DRM_OUTPUT_H #include "drm_pointer.h" #include "drm_object.h" #include #include #include #include #include #include namespace KWayland { namespace Server { class OutputInterface; class OutputDeviceInterface; class OutputChangeSet; class OutputManagementInterface; } } namespace KWin { class DrmBackend; class DrmBuffer; class DrmDumbBuffer; class DrmPlane; class DrmConnector; class DrmCrtc; class DrmOutput : public QObject { Q_OBJECT public: struct Edid { QByteArray eisaId; QByteArray monitorName; QByteArray serialNumber; QSize physicalSize; }; virtual ~DrmOutput(); void releaseGbm(); void showCursor(DrmDumbBuffer *buffer); void hideCursor(); void moveCursor(const QPoint &globalPos); bool init(drmModeConnector *connector); bool present(DrmBuffer *buffer); void pageFlipped(); /** * This sets the changes and tests them against the DRM output */ void setChanges(KWayland::Server::OutputChangeSet *changeset); bool commitChanges(); QSize pixelSize() const; qreal scale() const; /* * The geometry of this output in global compositor co-ordinates (i.e scaled) */ 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, Suspend = DRM_MODE_DPMS_SUSPEND, Off = DRM_MODE_DPMS_OFF }; 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 { return m_uuid; } Q_SIGNALS: void dpmsChanged(); private: friend class DrmBackend; friend class DrmCrtc; // TODO: For use of setModeLegacy. Remove later when we allow multiple connectors per crtc // 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); void initDpms(drmModeConnector *connector); bool isCurrentMode(const drmModeModeInfo *mode) const; void initUuid(); 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; DrmCrtc *m_crtc = nullptr; QPoint m_globalPos; qreal m_scale = 1; bool m_lastGbm = false; drmModeModeInfo m_mode; Edid m_edid; QPointer m_waylandOutput; QPointer m_waylandOutputDevice; QPointer 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 m_planesFlipList; + QVector m_nextPlanesFlipList; + bool m_pageFlipPending = false; + bool m_dpmsAtomicOffPending = false; + bool m_modesetRequested = true; }; } Q_DECLARE_METATYPE(KWin::DrmOutput*) #endif