From b50f7478769c1b50b48d5f0e9201a8e4b7e275ef Mon Sep 17 00:00:00 2001 From: Xaver Hugl Date: Mon, 5 Oct 2020 21:05:55 +0000 Subject: [PATCH] Multi-GPU output support on Wayland This commit sets up udev and the DrmBackend to list and use all GPUs. BUG: 425586 BUG: 417323 --- plugins/platforms/drm/CMakeLists.txt | 1 + plugins/platforms/drm/drm_backend.cpp | 350 +++++------------- plugins/platforms/drm/drm_backend.h | 61 +-- plugins/platforms/drm/drm_gpu.cpp | 273 ++++++++++++++ plugins/platforms/drm/drm_gpu.h | 111 ++++++ plugins/platforms/drm/drm_object_crtc.cpp | 16 +- plugins/platforms/drm/drm_object_crtc.h | 8 +- plugins/platforms/drm/drm_output.cpp | 75 ++-- plugins/platforms/drm/drm_output.h | 15 +- plugins/platforms/drm/egl_gbm_backend.cpp | 18 +- plugins/platforms/drm/egl_gbm_backend.h | 4 +- plugins/platforms/drm/egl_stream_backend.cpp | 19 +- plugins/platforms/drm/egl_stream_backend.h | 4 +- .../drm/scene_qpainter_drm_backend.cpp | 12 +- .../drm/scene_qpainter_drm_backend.h | 4 +- plugins/platforms/fbdev/fb_backend.cpp | 2 +- udev.cpp | 79 ++-- udev.h | 7 +- 18 files changed, 635 insertions(+), 424 deletions(-) create mode 100644 plugins/platforms/drm/drm_gpu.cpp create mode 100644 plugins/platforms/drm/drm_gpu.h diff --git a/plugins/platforms/drm/CMakeLists.txt b/plugins/platforms/drm/CMakeLists.txt index 87e916f6d..091ca287b 100644 --- a/plugins/platforms/drm/CMakeLists.txt +++ b/plugins/platforms/drm/CMakeLists.txt @@ -11,6 +11,7 @@ set(DRM_SOURCES logging.cpp scene_qpainter_drm_backend.cpp screens_drm.cpp + drm_gpu.cpp ) if (HAVE_GBM) diff --git a/plugins/platforms/drm/drm_backend.cpp b/plugins/platforms/drm/drm_backend.cpp index 8c2a687f5..6ec846c93 100644 --- a/plugins/platforms/drm/drm_backend.cpp +++ b/plugins/platforms/drm/drm_backend.cpp @@ -47,6 +47,8 @@ #include #include +#include "drm_gpu.h" + #ifndef DRM_CAP_CURSOR_WIDTH #define DRM_CAP_CURSOR_WIDTH 0x8 #endif @@ -77,21 +79,12 @@ DrmBackend::DrmBackend(QObject *parent) DrmBackend::~DrmBackend() { -#if HAVE_GBM - if (m_gbmDevice) { - gbm_device_destroy(m_gbmDevice); - } -#endif - if (m_fd >= 0) { + if (m_gpus.size() > 0) { // wait for pageflips while (m_pageFlipsPending != 0) { QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents); } - - qDeleteAll(m_planes); - qDeleteAll(m_crtcs); - qDeleteAll(m_connectors); - close(m_fd); + qDeleteAll(m_gpus); } } @@ -243,71 +236,45 @@ void DrmBackend::pageFlipHandler(int fd, unsigned int frame, unsigned int sec, u void DrmBackend::openDrm() { connect(LogindIntegration::self(), &LogindIntegration::sessionActiveChanged, this, &DrmBackend::activate); - UdevDevice::Ptr device = m_udev->primaryGpu(); - if (!device) { + std::vector devices = m_udev->listGPUs(); + if (devices.size() == 0) { qCWarning(KWIN_DRM) << "Did not find a GPU"; return; } - m_devNode = qEnvironmentVariableIsSet("KWIN_DRM_DEVICE_NODE") ? qgetenv("KWIN_DRM_DEVICE_NODE") : QByteArray(device->devNode()); - int fd = LogindIntegration::self()->takeDevice(m_devNode.constData()); - if (fd < 0) { - qCWarning(KWIN_DRM) << "failed to open drm device at" << m_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); + + for (unsigned int gpu_index = 0; gpu_index < devices.size(); gpu_index++) { + auto device = std::move(devices.at(gpu_index)); + auto devNode = QByteArray(device->devNode()); + int fd = LogindIntegration::self()->takeDevice(devNode.constData()); + if (fd < 0) { + qCWarning(KWIN_DRM) << "failed to open drm device at" << devNode; + return; } - ); - m_drmId = device->sysNum(); - + m_active = true; + QSocketNotifier *notifier = new QSocketNotifier(fd, QSocketNotifier::Read, this); + connect(notifier, &QSocketNotifier::activated, this, + [fd] { + 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(fd, &e); + } + ); + DrmGpu *gpu = new DrmGpu(this, devNode, fd, device->sysNum()); + connect(gpu, &DrmGpu::outputAdded, this, &DrmBackend::addOutput); + connect(gpu, &DrmGpu::outputRemoved, this, &DrmBackend::removeOutput); + m_gpus.append(gpu); + break; + } + // trying to activate Atomic Mode Setting (this means also Universal Planes) if (!qEnvironmentVariableIsSet("KWIN_DRM_NO_AMS")) { - if (drmSetClientCap(m_fd, DRM_CLIENT_CAP_ATOMIC, 1) == 0) { - qCDebug(KWIN_DRM) << "Using Atomic Mode Setting."; - m_atomicModeSetting = true; - - DrmScopedPointer 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) { - DrmScopedPointer kplane(drmModeGetPlane(m_fd, planeResources->planes[i])); - DrmPlane *p = new DrmPlane(kplane->plane_id, m_fd); - 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."; - } + for (auto gpu : m_gpus) + gpu->tryAMS(); } initCursor(); @@ -317,7 +284,7 @@ void DrmBackend::openDrm() if (m_outputs.isEmpty()) { qCDebug(KWIN_DRM) << "No connected outputs found on startup."; } - + // setup udevMonitor if (m_udevMonitor) { m_udevMonitor->filterSubsystemDevType("drm"); @@ -330,7 +297,14 @@ void DrmBackend::openDrm() if (!device) { return; } - if (device->sysNum() != m_drmId) { + bool drm = false; + for (auto gpu : m_gpus) { + if (gpu->drmId() == device->sysNum()) { + drm = true; + break; + } + } + if (!drm) { return; } if (device->hasProperty("HOTPLUG", "1")) { @@ -346,176 +320,34 @@ void DrmBackend::openDrm() setReady(true); } +void DrmBackend::addOutput(DrmOutput *o) +{ + m_outputs.append(o); + m_enabledOutputs.append(o); + emit o->gpu()->outputEnabled(o); +} + +void DrmBackend::removeOutput(DrmOutput *o) +{ + emit o->gpu()->outputDisabled(o); + m_outputs.removeOne(o); + m_enabledOutputs.removeOne(o); +} + bool DrmBackend::updateOutputs() { - if (m_fd < 0) { + if (m_gpus.size() == 0) { return false; } - - DrmScopedPointer resources(drmModeGetResources(m_fd)); - if (!resources) { - qCWarning(KWIN_DRM) << "drmModeGetResources failed"; - return false; - } - - auto oldConnectors = m_connectors; - for (int i = 0; i < resources->count_connectors; ++i) { - const uint32_t currentConnector = resources->connectors[i]; - auto it = std::find_if(m_connectors.constBegin(), m_connectors.constEnd(), [currentConnector] (DrmConnector *c) { return c->id() == currentConnector; }); - if (it == m_connectors.constEnd()) { - auto c = new DrmConnector(currentConnector, m_fd); - if (m_atomicModeSetting && !c->atomicInit()) { - delete c; - continue; - } - m_connectors << c; - } else { - oldConnectors.removeOne(*it); - } - } - - auto oldCrtcs = m_crtcs; - for (int i = 0; i < resources->count_crtcs; ++i) { - const uint32_t currentCrtc = resources->crtcs[i]; - auto it = std::find_if(m_crtcs.constBegin(), m_crtcs.constEnd(), [currentCrtc] (DrmCrtc *c) { return c->id() == currentCrtc; }); - if (it == m_crtcs.constEnd()) { - auto c = new DrmCrtc(currentCrtc, this, i); - if (m_atomicModeSetting && !c->atomicInit()) { - delete c; - continue; - } - m_crtcs << c; - } else { - oldCrtcs.removeOne(*it); - } - } - - for (auto c : qAsConst(oldConnectors)) { - m_connectors.removeOne(c); - } - for (auto c : qAsConst(oldCrtcs)) { - m_crtcs.removeOne(c); - } - - 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 - QVector removedOutputs; - auto it = m_outputs.begin(); - while (it != m_outputs.end()) { - if (connectedOutputs.contains(*it)) { - it++; - continue; - } - DrmOutput *removed = *it; - it = m_outputs.erase(it); - m_enabledOutputs.removeOne(removed); - emit outputRemoved(removed); - removedOutputs.append(removed); - } - - // now check new connections - for (DrmConnector *con : qAsConst(pendingConnectors)) { - DrmScopedPointer 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)) { - DrmScopedPointer 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 - DrmScopedPointer 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; - - 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(); - delete output; - continue; - } - if (!output->initCursor(m_cursorSize)) { - setSoftWareCursor(true); - } - 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; - m_enabledOutputs = connectedOutputs; + for (auto gpu : m_gpus) + gpu->updateOutputs(); + + std::sort(m_outputs.begin(), m_outputs.end(), [] (DrmOutput *a, DrmOutput *b) { return a->m_conn->id() < b->m_conn->id(); }); readOutputsConfiguration(); updateOutputsEnabled(); if (!m_outputs.isEmpty()) { emit screensQueried(); } - - for(DrmOutput* removedOutput : removedOutputs) { - removedOutput->teardown(); - removedOutput->m_crtc = nullptr; - removedOutput->m_conn = nullptr; - } - qDeleteAll(oldConnectors); - qDeleteAll(oldCrtcs); return true; } @@ -574,12 +406,12 @@ void DrmBackend::enableOutput(DrmOutput *output, bool enable) if (enable) { Q_ASSERT(!m_enabledOutputs.contains(output)); m_enabledOutputs << output; - emit outputAdded(output); + emit output->gpu()->outputEnabled(output); } else { Q_ASSERT(m_enabledOutputs.contains(output)); m_enabledOutputs.removeOne(output); Q_ASSERT(!m_enabledOutputs.contains(output)); - emit outputRemoved(output); + emit output->gpu()->outputDisabled(output); } updateOutputsEnabled(); checkOutputsAreOn(); @@ -640,6 +472,7 @@ void DrmBackend::initCursor() if (m_cursorEnabled) { if (!(*it)->showCursor()) { setSoftWareCursor(true); + break; } } else { (*it)->hideCursor(); @@ -649,15 +482,17 @@ void DrmBackend::initCursor() ); uint64_t capability = 0; QSize cursorSize; - if (drmGetCap(m_fd, DRM_CAP_CURSOR_WIDTH, &capability) == 0) { - cursorSize.setWidth(capability); - } else { - cursorSize.setWidth(64); + cursorSize.setWidth(64); + for (auto gpu : m_gpus) { + if (drmGetCap(gpu->fd(), DRM_CAP_CURSOR_WIDTH, &capability) == 0) { + cursorSize.setWidth(capability); + } } - if (drmGetCap(m_fd, DRM_CAP_CURSOR_HEIGHT, &capability) == 0) { - cursorSize.setHeight(capability); - } else { - cursorSize.setHeight(64); + cursorSize.setHeight(64); + for (auto gpu : m_gpus) { + if (drmGetCap(gpu->fd(), DRM_CAP_CURSOR_HEIGHT, &capability) == 0) { + cursorSize.setHeight(capability); + } } m_cursorSize = cursorSize; // now we have screens and can set cursors, so start tracking @@ -735,7 +570,7 @@ Screens *DrmBackend::createScreens(QObject *parent) QPainterBackend *DrmBackend::createQPainterBackend() { m_deleteBufferAfterPageFlip = false; - return new DrmQPainterBackend(this); + return new DrmQPainterBackend(this, m_gpus.at(0)); } OpenGLBackend *DrmBackend::createOpenGLBackend() @@ -743,32 +578,18 @@ OpenGLBackend *DrmBackend::createOpenGLBackend() #if HAVE_EGL_STREAMS if (m_useEglStreams) { m_deleteBufferAfterPageFlip = false; - return new EglStreamBackend(this); + return new EglStreamBackend(this, m_gpus.at(0)); } #endif #if HAVE_GBM m_deleteBufferAfterPageFlip = true; - return new EglGbmBackend(this); + return new EglGbmBackend(this, m_gpus.at(0)); #else return Platform::createOpenGLBackend(); #endif } -DrmDumbBuffer *DrmBackend::createBuffer(const QSize &size) -{ - DrmDumbBuffer *b = new DrmDumbBuffer(m_fd, size); - return b; -} - -#if HAVE_GBM -DrmSurfaceBuffer *DrmBackend::createBuffer(const std::shared_ptr &surface) -{ - DrmSurfaceBuffer *b = new DrmSurfaceBuffer(m_fd, surface); - return b; -} -#endif - void DrmBackend::updateOutputsEnabled() { bool enabled = false; @@ -801,7 +622,9 @@ QString DrmBackend::supportInformation() const s.nospace(); s << "Name: " << "DRM" << Qt::endl; s << "Active: " << m_active << Qt::endl; - s << "Atomic Mode Setting: " << m_atomicModeSetting << Qt::endl; + for (int g = 0; g < m_gpus.size(); g++) { + s << "Atomic Mode Setting on GPU " << g << ": " << m_gpus.at(g)->atomicModeSetting() << Qt::endl; + } #if HAVE_EGL_STREAMS s << "Using EGL Streams: " << m_useEglStreams << Qt::endl; #endif @@ -811,7 +634,10 @@ QString DrmBackend::supportInformation() const DmaBufTexture *DrmBackend::createDmaBufTexture(const QSize &size) { #if HAVE_GBM - return GbmDmaBuf::createBuffer(size, m_gbmDevice); + // gpu_index is a fixed 0 here + // as the first GPU is assumed to always be the one used for scene rendering + // and this function is only used for Pipewire + return GbmDmaBuf::createBuffer(size, m_gpus.at(0)->gbmDevice()); #else return nullptr; #endif diff --git a/plugins/platforms/drm/drm_backend.h b/plugins/platforms/drm/drm_backend.h index 54669f9fd..9e84dd41d 100644 --- a/plugins/platforms/drm/drm_backend.h +++ b/plugins/platforms/drm/drm_backend.h @@ -43,6 +43,7 @@ class DrmCrtc; class DrmConnector; class GbmSurface; class Cursor; +class DrmGpu; class KWIN_EXPORT DrmBackend : public Platform { @@ -60,16 +61,9 @@ public: void init() override; void prepareShutdown() override; - - DrmDumbBuffer *createBuffer(const QSize &size); -#if HAVE_GBM - DrmSurfaceBuffer *createBuffer(const std::shared_ptr &surface); -#endif + bool present(DrmBuffer *buffer, DrmOutput *output); - int fd() const { - return m_fd; - } Outputs outputs() const override; Outputs enabledOutputs() const override; QVector drmOutputs() const { @@ -81,13 +75,6 @@ public: void enableOutput(DrmOutput *output, bool enable); - QVector planes() const { - return m_planes; - } - QVector overlayPlanes() const { - return m_overlayPlanes; - } - void createDpmsFilter(); void checkOutputsAreOn(); @@ -95,21 +82,6 @@ public: 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; - } - - QByteArray devNode() const { - return m_devNode; - } #if HAVE_EGL_STREAMS bool useEglStreams() const { @@ -128,22 +100,13 @@ public: public Q_SLOTS: void turnOutputsOn(); -Q_SIGNALS: - /** - * Emitted whenever an output is removed/disabled - */ - void outputRemoved(KWin::DrmOutput *output); - /** - * Emitted whenever an output is added/enabled - */ - void outputAdded(KWin::DrmOutput *output); - protected: - void doHideCursor() override; void doShowCursor() override; - private: + friend class DrmGpu; + void addOutput(DrmOutput* output); + void removeOutput(DrmOutput* output); static void pageFlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data); void openDrm(); void activate(bool active); @@ -161,32 +124,22 @@ private: void updateOutputsEnabled(); QScopedPointer m_udev; QScopedPointer m_udevMonitor; - int m_fd = -1; - int m_drmId = 0; - // all crtcs - QVector m_crtcs; - // all connectors - QVector m_connectors; + // active output pipelines (planes + crtc + encoder + connector) QVector m_outputs; // active and enabled pipelines (above + wl_output) QVector m_enabledOutputs; bool m_deleteBufferAfterPageFlip; - bool m_atomicModeSetting = false; bool m_cursorEnabled = false; QSize m_cursorSize; int m_pageFlipsPending = 0; bool m_active = false; - QByteArray m_devNode; #if HAVE_EGL_STREAMS bool m_useEglStreams = false; #endif - // all available planes: primarys, cursors and overlays - QVector m_planes; - QVector m_overlayPlanes; + QVector m_gpus; QScopedPointer m_dpmsFilter; - gbm_device *m_gbmDevice = nullptr; }; diff --git a/plugins/platforms/drm/drm_gpu.cpp b/plugins/platforms/drm/drm_gpu.cpp new file mode 100644 index 000000000..9eeecca70 --- /dev/null +++ b/plugins/platforms/drm/drm_gpu.cpp @@ -0,0 +1,273 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2020 Xaver Hugl + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "drm_gpu.h" + +#include "drm_backend.h" +#include "drm_output.h" +#include "drm_object_connector.h" +#include "drm_object_crtc.h" +#include "abstract_egl_backend.h" +#include "logging.h" + +#if HAVE_GBM +#include "egl_gbm_backend.h" +#include +#include "gbm_dmabuf.h" +#endif +// system +#include +#include +// drm +#include +#include +#include + +namespace KWin +{ + +DrmGpu::DrmGpu(DrmBackend *backend, QByteArray devNode, int fd, int drmId) : m_backend(backend), m_devNode(devNode), m_fd(fd), m_drmId(drmId), m_atomicModeSetting(false), m_useEglStreams(false), m_gbmDevice(nullptr) +{ + +} + +DrmGpu::~DrmGpu() +{ +#if HAVE_GBM + gbm_device_destroy(m_gbmDevice); +#endif + qDeleteAll(m_crtcs); + qDeleteAll(m_connectors); + qDeleteAll(m_planes); + close(m_fd); +} + +void DrmGpu::tryAMS() +{ + m_atomicModeSetting = false; + if (drmSetClientCap(m_fd, DRM_CLIENT_CAP_ATOMIC, 1) == 0) { + bool ams = true; + QVector planes, overlayPlanes; + + DrmScopedPointer planeResources(drmModeGetPlaneResources(m_fd)); + if (!planeResources) { + qCWarning(KWIN_DRM) << "Failed to get plane resources. Falling back to legacy mode on GPU " << m_devNode; + ams = false; + } + if (ams) { + qCDebug(KWIN_DRM) << "Using Atomic Mode Setting on gpu" << m_devNode; + qCDebug(KWIN_DRM) << "Number of planes on GPU" << m_devNode << ":" << planeResources->count_planes; + + // create the plane objects + for (unsigned int i = 0; i < planeResources->count_planes; ++i) { + DrmScopedPointer kplane(drmModeGetPlane(m_fd, planeResources->planes[i])); + DrmPlane *p = new DrmPlane(kplane->plane_id, m_fd); + if (p->atomicInit()) { + planes << p; + if (p->type() == DrmPlane::TypeIndex::Overlay) { + overlayPlanes << p; + } + } else { + delete p; + } + } + + if (planes.isEmpty()) { + qCWarning(KWIN_DRM) << "Failed to create any plane. Falling back to legacy mode on GPU " << m_devNode; + ams = false; + } + } + if (!ams) { + for (auto p : planes) { + delete p; + } + planes.clear(); + overlayPlanes.clear(); + } + m_atomicModeSetting = ams; + m_planes = planes; + m_overlayPlanes = overlayPlanes; + } else { + qCWarning(KWIN_DRM) << "drmSetClientCap for Atomic Mode Setting failed. Using legacy mode on GPU" << m_devNode; + } +} + +bool DrmGpu::updateOutputs() +{ + auto oldConnectors = m_connectors; + auto oldCrtcs = m_crtcs; + DrmScopedPointer resources(drmModeGetResources(m_fd)); + if (!resources) { + qCWarning(KWIN_DRM) << "drmModeGetResources failed"; + return false; + } + + for (int i = 0; i < resources->count_connectors; ++i) { + const uint32_t currentConnector = resources->connectors[i]; + auto it = std::find_if(m_connectors.constBegin(), m_connectors.constEnd(), [currentConnector] (DrmConnector *c) { return c->id() == currentConnector; }); + if (it == m_connectors.constEnd()) { + auto c = new DrmConnector(currentConnector, m_fd); + if (m_atomicModeSetting && !c->atomicInit()) { + delete c; + continue; + } + m_connectors << c; + } else { + oldConnectors.removeOne(*it); + } + } + + for (int i = 0; i < resources->count_crtcs; ++i) { + const uint32_t currentCrtc = resources->crtcs[i]; + auto it = std::find_if(m_crtcs.constBegin(), m_crtcs.constEnd(), [currentCrtc] (DrmCrtc *c) { return c->id() == currentCrtc; }); + if (it == m_crtcs.constEnd()) { + auto c = new DrmCrtc(currentCrtc, m_backend, this, i); + if (m_atomicModeSetting && !c->atomicInit()) { + delete c; + continue; + } + m_crtcs << c; + } else { + oldCrtcs.removeOne(*it); + } + } + + for (auto c : qAsConst(oldConnectors)) { + m_connectors.removeOne(c); + } + for (auto c : qAsConst(oldCrtcs)) { + m_crtcs.removeOne(c); + } + + 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 + QVector removedOutputs; + auto it = m_outputs.begin(); + while (it != m_outputs.end()) { + if (connectedOutputs.contains(*it)) { + it++; + continue; + } + DrmOutput *removed = *it; + it = m_outputs.erase(it); + removedOutputs.append(removed); + } + + for (DrmConnector *con : qAsConst(pendingConnectors)) { + DrmScopedPointer 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)) { + DrmScopedPointer 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 + DrmScopedPointer modeCrtc(drmModeGetCrtc(m_fd, crtc->id())); + if (!modeCrtc) { + continue; + } + + DrmOutput *output = new DrmOutput(this->m_backend, this); + con->setOutput(output); + output->m_conn = con; + crtc->setOutput(output); + output->m_crtc = crtc; + + 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(); + delete output; + continue; + } + if (!output->initCursor(m_backend->m_cursorSize)) { + m_backend->setSoftWareCursor(true); + } + 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; + + for(DrmOutput *removedOutput : removedOutputs) { + emit outputRemoved(removedOutput); + removedOutput->teardown(); + removedOutput->m_crtc = nullptr; + removedOutput->m_conn = nullptr; + } + + qDeleteAll(oldConnectors); + qDeleteAll(oldCrtcs); + return true; +} + +DrmOutput *DrmGpu::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; +} + +} diff --git a/plugins/platforms/drm/drm_gpu.h b/plugins/platforms/drm/drm_gpu.h new file mode 100644 index 000000000..9df8a73c0 --- /dev/null +++ b/plugins/platforms/drm/drm_gpu.h @@ -0,0 +1,111 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2020 Xaver Hugl + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef DRM_GPU_H +#define DRM_GPU_H + +#include +#include + +#include "drm_buffer.h" + +struct gbm_device; + +namespace KWin +{ + +class DrmOutput; +class DrmPlane; +class DrmCrtc; +class DrmConnector; +class DrmBackend; +class AbstractEglBackend; + +class DrmGpu : public QObject +{ + Q_OBJECT +public: + DrmGpu(DrmBackend *backend, QByteArray devNode, int fd, int drmId); + ~DrmGpu(); + + // getters + QVector outputs() const { + return m_outputs; + } + + int fd() const { + return m_fd; + } + + int drmId() const { + return m_drmId; + } + + bool atomicModeSetting() const { + return m_atomicModeSetting; + } + + QByteArray devNode() const { + return m_devNode; + } + + gbm_device *gbmDevice() const { + return m_gbmDevice; + } + + QVector planes() const { + return m_planes; + } + + void setGbmDevice(gbm_device *d) { + m_gbmDevice = d; + } + + DrmDumbBuffer *createBuffer(const QSize &size) const { + return new DrmDumbBuffer(m_fd, size); + } + +Q_SIGNALS: + void outputAdded(DrmOutput *output); + void outputRemoved(DrmOutput *output); + void outputEnabled(DrmOutput *output); + void outputDisabled(DrmOutput *output); + +protected: + + friend class DrmBackend; + void tryAMS(); + bool updateOutputs(); + +private: + DrmOutput *findOutput(quint32 connector); + + DrmBackend* const m_backend; + + const QByteArray m_devNode; + const int m_fd; + const int m_drmId; + bool m_atomicModeSetting; + bool m_useEglStreams; + gbm_device* m_gbmDevice; + +// all available planes: primarys, cursors and overlays + QVector m_planes; + QVector m_overlayPlanes; + // crtcs + QVector m_crtcs; + // connectors + QVector m_connectors; + // active output pipelines (planes + crtc + encoder + connector) + QVector m_outputs; +}; + +} + +#endif // DRM_GPU_H diff --git a/plugins/platforms/drm/drm_object_crtc.cpp b/plugins/platforms/drm/drm_object_crtc.cpp index 4f8ff869f..d198ad37b 100644 --- a/plugins/platforms/drm/drm_object_crtc.cpp +++ b/plugins/platforms/drm/drm_object_crtc.cpp @@ -12,16 +12,18 @@ #include "drm_buffer.h" #include "drm_pointer.h" #include "logging.h" +#include "drm_gpu.h" namespace KWin { -DrmCrtc::DrmCrtc(uint32_t crtc_id, DrmBackend *backend, int resIndex) - : DrmObject(crtc_id, backend->fd()), +DrmCrtc::DrmCrtc(uint32_t crtc_id, DrmBackend *backend, DrmGpu *gpu, int resIndex) + : DrmObject(crtc_id, gpu->fd()), m_resIndex(resIndex), - m_backend(backend) + m_backend(backend), + m_gpu(gpu) { - DrmScopedPointer modeCrtc(drmModeGetCrtc(backend->fd(), crtc_id)); + DrmScopedPointer modeCrtc(drmModeGetCrtc(gpu->fd(), crtc_id)); if (modeCrtc) { m_gammaRampSize = modeCrtc->gamma_size; } @@ -81,12 +83,12 @@ bool DrmCrtc::blank() return false; } - if (m_backend->atomicModeSetting()) { + if (m_gpu->atomicModeSetting()) { return false; } if (!m_blackBuffer) { - DrmDumbBuffer *blackBuffer = m_backend->createBuffer(m_output->pixelSize()); + DrmDumbBuffer *blackBuffer = m_gpu->createBuffer(m_output->pixelSize()); if (!blackBuffer->map()) { delete blackBuffer; return false; @@ -113,7 +115,7 @@ bool DrmCrtc::setGammaRamp(const GammaRamp &gamma) uint16_t *green = const_cast(gamma.green()); uint16_t *blue = const_cast(gamma.blue()); - const bool isError = drmModeCrtcSetGamma(m_backend->fd(), m_id, + const bool isError = drmModeCrtcSetGamma(m_gpu->fd(), m_id, gamma.size(), red, green, blue); return !isError; diff --git a/plugins/platforms/drm/drm_object_crtc.h b/plugins/platforms/drm/drm_object_crtc.h index c750ea2ba..e4231727d 100644 --- a/plugins/platforms/drm/drm_object_crtc.h +++ b/plugins/platforms/drm/drm_object_crtc.h @@ -18,11 +18,12 @@ class DrmBackend; class DrmBuffer; class DrmDumbBuffer; class GammaRamp; +class DrmGpu; class DrmCrtc : public DrmObject { public: - DrmCrtc(uint32_t crtc_id, DrmBackend *backend, int resIndex); + DrmCrtc(uint32_t crtc_id, DrmBackend *backend, DrmGpu *gpu, int resIndex); ~DrmCrtc() override; @@ -57,6 +58,10 @@ public: return m_gammaRampSize; } bool setGammaRamp(const GammaRamp &gamma); + + DrmGpu *gpu() { + return m_gpu; + } private: int m_resIndex; @@ -66,6 +71,7 @@ private: DrmBuffer *m_nextBuffer = nullptr; DrmDumbBuffer *m_blackBuffer = nullptr; DrmBackend *m_backend; + DrmGpu *m_gpu; }; } diff --git a/plugins/platforms/drm/drm_output.cpp b/plugins/platforms/drm/drm_output.cpp index 5a0bb4a90..8d081d8e2 100644 --- a/plugins/platforms/drm/drm_output.cpp +++ b/plugins/platforms/drm/drm_output.cpp @@ -36,12 +36,15 @@ #include #include +#include "drm_gpu.h" + namespace KWin { -DrmOutput::DrmOutput(DrmBackend *backend) +DrmOutput::DrmOutput(DrmBackend *backend, DrmGpu *gpu) : AbstractWaylandOutput(backend) , m_backend(backend) + , m_gpu(gpu) { } @@ -96,13 +99,13 @@ void DrmOutput::releaseGbm() bool DrmOutput::hideCursor() { - return drmModeSetCursor(m_backend->fd(), m_crtc->id(), 0, 0, 0) == 0; + return drmModeSetCursor(m_gpu->fd(), m_crtc->id(), 0, 0, 0) == 0; } bool DrmOutput::showCursor(DrmDumbBuffer *c) { const QSize &s = c->size(); - return drmModeSetCursor(m_backend->fd(), m_crtc->id(), c->handle(), s.width(), s.height()) == 0; + return drmModeSetCursor(m_gpu->fd(), m_crtc->id(), c->handle(), s.width(), s.height()) == 0; } bool DrmOutput::showCursor() @@ -118,6 +121,7 @@ bool DrmOutput::showCursor() const bool ret = showCursor(m_cursor[m_cursorIndex].data()); if (!ret) { + qCDebug(KWIN_DRM) << "DrmOutput::showCursor(DrmDumbBuffer) failed"; return ret; } @@ -164,7 +168,7 @@ void DrmOutput::updateCursor() p.end(); } -void DrmOutput::moveCursor(Cursor* cursor, const QPoint &globalPos) +void DrmOutput::moveCursor(Cursor *cursor, const QPoint &globalPos) { const QMatrix4x4 hotspotMatrix = matrixDisplay(cursor->image().size()); @@ -194,7 +198,7 @@ void DrmOutput::moveCursor(Cursor* cursor, const QPoint &globalPos) } pos *= scale(); pos -= hotspotMatrix.map(cursor->hotspot()); - drmModeMoveCursor(m_backend->fd(), m_crtc->id(), pos.x(), pos.y()); + drmModeMoveCursor(m_gpu->fd(), m_crtc->id(), pos.x(), pos.y()); } static QHash s_connectorNames = { @@ -244,7 +248,7 @@ bool DrmOutput::init(drmModeConnector *connector) initEdid(connector); initDpms(connector); initUuid(); - if (m_backend->atomicModeSetting()) { + if (m_gpu->atomicModeSetting()) { if (!initPrimaryPlane()) { return false; } @@ -255,7 +259,7 @@ bool DrmOutput::init(drmModeConnector *connector) setDpmsSupported(true); initOutputDevice(connector); - if (!m_backend->atomicModeSetting() && !m_crtc->blank()) { + if (!m_gpu->atomicModeSetting() && !m_crtc->blank()) { // We use legacy mode and the initial output blank failed. return false; } @@ -361,12 +365,12 @@ void DrmOutput::initEdid(drmModeConnector *connector) { DrmScopedPointer edid; for (int i = 0; i < connector->count_props; ++i) { - DrmScopedPointer property(drmModeGetProperty(m_backend->fd(), connector->props[i])); + DrmScopedPointer property(drmModeGetProperty(m_gpu->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])); + edid.reset(drmModeGetPropertyBlob(m_gpu->fd(), connector->prop_values[i])); } } if (!edid) { @@ -381,8 +385,8 @@ void DrmOutput::initEdid(drmModeConnector *connector) bool DrmOutput::initPrimaryPlane() { - for (int i = 0; i < m_backend->planes().size(); ++i) { - DrmPlane* p = m_backend->planes()[i]; + for (int i = 0; i < m_gpu->planes().size(); ++i) { + DrmPlane *p = m_gpu->planes()[i]; if (!p) { continue; } @@ -409,8 +413,8 @@ bool DrmOutput::initPrimaryPlane() 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]; + for (int i = 0; i < m_gpu->planes().size(); ++i) { + DrmPlane *p = m_gpu->planes()[i]; if (!p) { continue; } @@ -437,7 +441,7 @@ bool DrmOutput::initCursorPlane() // TODO: Add call in init (but needs lay bool DrmOutput::initCursor(const QSize &cursorSize) { auto createCursor = [this, cursorSize] (int index) { - m_cursor[index].reset(m_backend->createBuffer(cursorSize)); + m_cursor[index].reset(m_gpu->createBuffer(cursorSize)); if (!m_cursor[index]->map(QImage::Format_ARGB32_Premultiplied)) { return false; } @@ -452,7 +456,7 @@ bool DrmOutput::initCursor(const QSize &cursorSize) void DrmOutput::initDpms(drmModeConnector *connector) { for (int i = 0; i < connector->count_props; ++i) { - DrmScopedPointer property(drmModeGetProperty(m_backend->fd(), connector->props[i])); + DrmScopedPointer property(drmModeGetProperty(m_gpu->fd(), connector->props[i])); if (!property) { continue; } @@ -467,7 +471,7 @@ void DrmOutput::updateEnablement(bool enable) { if (enable) { m_dpmsModePending = DpmsMode::On; - if (m_backend->atomicModeSetting()) { + if (m_gpu->atomicModeSetting()) { atomicEnable(); } else { if (dpmsLegacyApply()) { @@ -477,7 +481,7 @@ void DrmOutput::updateEnablement(bool enable) } else { m_dpmsModePending = DpmsMode::Off; - if (m_backend->atomicModeSetting()) { + if (m_gpu->atomicModeSetting()) { atomicDisable(); } else { if (dpmsLegacyApply()) { @@ -563,7 +567,7 @@ void DrmOutput::updateDpms(KWaylandServer::OutputInterface::DpmsMode mode) m_dpmsModePending = drmMode; - if (m_backend->atomicModeSetting()) { + if (m_gpu->atomicModeSetting()) { m_modesetRequested = true; if (drmMode == DpmsMode::On) { if (m_atomicOffPending) { @@ -589,7 +593,7 @@ void DrmOutput::dpmsFinishOn() waylandOutput()->setDpmsMode(toWaylandDpmsMode(DpmsMode::On)); m_backend->checkOutputsAreOn(); - if (!m_backend->atomicModeSetting()) { + if (!m_gpu->atomicModeSetting()) { m_crtc->blank(); } if (Compositor *compositor = Compositor::self()) { @@ -611,7 +615,7 @@ void DrmOutput::dpmsFinishOff() bool DrmOutput::dpmsLegacyApply() { - if (drmModeConnectorSetProperty(m_backend->fd(), m_conn->id(), + if (drmModeConnectorSetProperty(m_gpu->fd(), m_conn->id(), m_dpms->prop_id, uint64_t(m_dpmsModePending)) < 0) { m_dpmsModePending = m_dpmsMode; qCWarning(KWIN_DRM) << "Setting DPMS failed"; @@ -693,7 +697,7 @@ void DrmOutput::updateTransform(Transform transform) void DrmOutput::updateMode(int modeIndex) { // get all modes on the connector - DrmScopedPointer connector(drmModeGetConnector(m_backend->fd(), m_conn->id())); + DrmScopedPointer connector(drmModeGetConnector(m_gpu->fd(), m_conn->id())); if (connector->count_modes <= modeIndex) { // TODO: error? return; @@ -716,7 +720,7 @@ void DrmOutput::setWaylandMode() void DrmOutput::pageFlipped() { // In legacy mode we might get a page flip through a blank. - Q_ASSERT(m_pageFlipPending || !m_backend->atomicModeSetting()); + Q_ASSERT(m_pageFlipPending || !m_gpu->atomicModeSetting()); m_pageFlipPending = false; if (m_deleted) { @@ -730,7 +734,7 @@ void DrmOutput::pageFlipped() // 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_gpu->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 @@ -755,7 +759,7 @@ void DrmOutput::pageFlipped() m_crtc->flipBuffer(); } } else { - if (m_backend->atomicModeSetting()){ + if (m_gpu->atomicModeSetting()){ for (DrmPlane *p : m_nextPlanesFlipList) { p->flipBuffer(); } @@ -776,7 +780,7 @@ bool DrmOutput::present(DrmBuffer *buffer) if (m_dpmsModePending != DpmsMode::On) { return false; } - if (m_backend->atomicModeSetting()) { + if (m_gpu->atomicModeSetting()) { return presentAtomically(buffer); } else { return presentLegacy(buffer); @@ -826,7 +830,7 @@ bool DrmOutput::presentAtomically(DrmBuffer *buffer) return true; } #endif - + m_primaryPlane->setNext(buffer); m_nextPlanesFlipList << m_primaryPlane; @@ -890,7 +894,7 @@ bool DrmOutput::presentLegacy(DrmBuffer *buffer) return false; } } - const bool ok = drmModePageFlip(m_backend->fd(), m_crtc->id(), buffer->bufferId(), DRM_MODE_PAGE_FLIP_EVENT, this) == 0; + const bool ok = drmModePageFlip(m_gpu->fd(), m_crtc->id(), buffer->bufferId(), DRM_MODE_PAGE_FLIP_EVENT, this) == 0; if (ok) { m_crtc->setNext(buffer); } else { @@ -902,7 +906,7 @@ bool DrmOutput::presentLegacy(DrmBuffer *buffer) 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) { + if (drmModeSetCrtc(m_gpu->fd(), m_crtc->id(), buffer->bufferId(), 0, 0, &connId, 1, &m_mode) == 0) { return true; } else { qCWarning(KWIN_DRM) << "Mode setting failed"; @@ -939,17 +943,16 @@ bool DrmOutput::doAtomicCommit(AtomicCommitMode mode) }; if (!req) { - qCWarning(KWIN_DRM) << "DRM: couldn't allocate atomic request"; - errorHandler(); - return false; + 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) { + if (drmModeCreatePropertyBlob(m_gpu->fd(), &m_mode, sizeof(m_mode), &m_blobId) != 0) { qCWarning(KWIN_DRM) << "Failed to create property blob"; errorHandler(); return false; @@ -994,8 +997,8 @@ bool DrmOutput::doAtomicCommit(AtomicCommitMode mode) return false; } - if (drmModeAtomicCommit(m_backend->fd(), req, flags, this)) { - qCWarning(KWIN_DRM) << "Atomic request failed to commit:" << strerror(errno); + if (drmModeAtomicCommit(m_gpu->fd(), req, flags, this)) { + qCDebug(KWIN_DRM) << "Atomic request failed to commit: " << strerror(errno); errorHandler(); return false; } @@ -1073,7 +1076,7 @@ bool DrmOutput::setGammaRamp(const GammaRamp &gamma) } -QDebug& operator<<(QDebug& s, const KWin::DrmOutput* output) +QDebug& operator<<(QDebug& s, const KWin::DrmOutput *output) { if (!output) return s.nospace() << "DrmOutput()"; diff --git a/plugins/platforms/drm/drm_output.h b/plugins/platforms/drm/drm_output.h index 4925c846a..7220c3152 100644 --- a/plugins/platforms/drm/drm_output.h +++ b/plugins/platforms/drm/drm_output.h @@ -31,6 +31,7 @@ class DrmPlane; class DrmConnector; class DrmCrtc; class Cursor; +class DrmGpu; class KWIN_EXPORT DrmOutput : public AbstractWaylandOutput { @@ -45,7 +46,7 @@ public: bool showCursor(); bool hideCursor(); void updateCursor(); - void moveCursor(Cursor* cursor, const QPoint &globalPos); + void moveCursor(Cursor *cursor, const QPoint &globalPos); bool init(drmModeConnector *connector); bool present(DrmBuffer *buffer); void pageFlipped(); @@ -91,12 +92,17 @@ public: * @return true if the hardware realizes the transform without further assistance */ bool hardwareTransforms() const; + + DrmGpu *gpu() { + return m_gpu; + } private: + friend class DrmGpu; 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); + DrmOutput(DrmBackend *backend, DrmGpu* gpu); bool presentAtomically(DrmBuffer *buffer); @@ -139,6 +145,7 @@ private: QMatrix4x4 matrixDisplay(const QSize &s) const; DrmBackend *m_backend; + DrmGpu *m_gpu; DrmConnector *m_conn = nullptr; DrmCrtc *m_crtc = nullptr; bool m_lastGbm = false; @@ -150,8 +157,8 @@ private: QByteArray m_uuid; uint32_t m_blobId = 0; - DrmPlane* m_primaryPlane = nullptr; - DrmPlane* m_cursorPlane = nullptr; + DrmPlane *m_primaryPlane = nullptr; + DrmPlane *m_cursorPlane = nullptr; QVector m_nextPlanesFlipList; bool m_pageFlipPending = false; bool m_atomicOffPending = false; diff --git a/plugins/platforms/drm/egl_gbm_backend.cpp b/plugins/platforms/drm/egl_gbm_backend.cpp index 311b08fa2..6188f5f3f 100644 --- a/plugins/platforms/drm/egl_gbm_backend.cpp +++ b/plugins/platforms/drm/egl_gbm_backend.cpp @@ -15,6 +15,7 @@ #include "logging.h" #include "options.h" #include "screens.h" +#include "drm_gpu.h" // kwin libs #include #include @@ -24,15 +25,16 @@ namespace KWin { -EglGbmBackend::EglGbmBackend(DrmBackend *drmBackend) +EglGbmBackend::EglGbmBackend(DrmBackend *drmBackend, DrmGpu *gpu) : AbstractEglBackend() , m_backend(drmBackend) + , m_gpu(gpu) { // Egl is always direct rendering. setIsDirectRendering(true); setSyncsToVBlank(true); - connect(m_backend, &DrmBackend::outputAdded, this, &EglGbmBackend::createOutput); - connect(m_backend, &DrmBackend::outputRemoved, this, &EglGbmBackend::removeOutput); + connect(m_gpu, &DrmGpu::outputEnabled, this, &EglGbmBackend::createOutput); + connect(m_gpu, &DrmGpu::outputDisabled, this, &EglGbmBackend::removeOutput); } EglGbmBackend::~EglGbmBackend() @@ -72,7 +74,7 @@ void EglGbmBackend::cleanupOutput(Output &output) bool EglGbmBackend::initializeEgl() { initClientExtensions(); - EGLDisplay display = m_backend->sceneEglDisplay(); + EGLDisplay display = eglDisplay(); // Use eglGetPlatformDisplayEXT() to get the display pointer // if the implementation supports it. @@ -88,12 +90,12 @@ bool EglGbmBackend::initializeEgl() return false; } - auto device = gbm_create_device(m_backend->fd()); + auto device = gbm_create_device(m_gpu->fd()); if (!device) { setFailed("Could not create gbm device"); return false; } - m_backend->setGbmDevice(device); + m_gpu->setGbmDevice(device); display = eglGetPlatformDisplayEXT(platform, device, nullptr); } @@ -147,7 +149,7 @@ bool EglGbmBackend::initRenderingContext() std::shared_ptr EglGbmBackend::createGbmSurface(const QSize &size) const { - auto gbmSurface = std::make_shared(m_backend->gbmDevice(), + auto gbmSurface = std::make_shared(m_gpu->gbmDevice(), size.width(), size.height(), GBM_FORMAT_XRGB8888, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); @@ -460,7 +462,7 @@ void EglGbmBackend::presentOnOutput(Output &output, const QRegion &damagedRegion } else { eglSwapBuffers(eglDisplay(), output.eglSurface); } - output.buffer = m_backend->createBuffer(output.gbmSurface); + output.buffer = new DrmSurfaceBuffer(m_gpu->fd(), output.gbmSurface); Q_EMIT output.output->outputChange(damagedRegion); m_backend->present(output.buffer, output.output); diff --git a/plugins/platforms/drm/egl_gbm_backend.h b/plugins/platforms/drm/egl_gbm_backend.h index b4d991aad..b8b8e8b92 100644 --- a/plugins/platforms/drm/egl_gbm_backend.h +++ b/plugins/platforms/drm/egl_gbm_backend.h @@ -22,6 +22,7 @@ class DrmBuffer; class DrmSurfaceBuffer; class DrmOutput; class GbmSurface; +class DrmGpu; /** * @brief OpenGL Backend using Egl on a GBM surface. @@ -30,7 +31,7 @@ class EglGbmBackend : public AbstractEglBackend { Q_OBJECT public: - EglGbmBackend(DrmBackend *drmBackend); + EglGbmBackend(DrmBackend *drmBackend, DrmGpu *gpu); ~EglGbmBackend() override; void screenGeometryChanged(const QSize &size) override; SceneOpenGLTexturePrivate *createBackendTexture(SceneOpenGLTexture *texture) override; @@ -93,6 +94,7 @@ private: void cleanupFramebuffer(Output &output); DrmBackend *m_backend; + DrmGpu *m_gpu; QVector m_outputs; friend class EglGbmTexture; }; diff --git a/plugins/platforms/drm/egl_stream_backend.cpp b/plugins/platforms/drm/egl_stream_backend.cpp index 70d3250bc..a166fb962 100644 --- a/plugins/platforms/drm/egl_stream_backend.cpp +++ b/plugins/platforms/drm/egl_stream_backend.cpp @@ -26,6 +26,7 @@ #include #include #include +#include "drm_gpu.h" namespace KWin { @@ -71,13 +72,13 @@ PFNEGLQUERYWAYLANDBUFFERWL pEglQueryWaylandBufferWL = nullptr; #define EGL_WAYLAND_Y_INVERTED_WL 0x31DB #endif -EglStreamBackend::EglStreamBackend(DrmBackend *b) - : AbstractEglBackend(), m_backend(b) +EglStreamBackend::EglStreamBackend(DrmBackend *b, DrmGpu *gpu) + : AbstractEglBackend(), m_backend(b), m_gpu(gpu) { setIsDirectRendering(true); setSyncsToVBlank(true); - connect(m_backend, &DrmBackend::outputAdded, this, &EglStreamBackend::createOutput); - connect(m_backend, &DrmBackend::outputRemoved, this, + connect(m_gpu, &DrmGpu::outputEnabled, this, &EglStreamBackend::createOutput); + connect(m_gpu, &DrmGpu::outputDisabled, this, [this] (DrmOutput *output) { auto it = std::find_if(m_outputs.begin(), m_outputs.end(), [output] (const Output &o) { @@ -120,7 +121,7 @@ void EglStreamBackend::cleanupOutput(const Output &o) bool EglStreamBackend::initializeEgl() { initClientExtensions(); - EGLDisplay display = m_backend->sceneEglDisplay(); + EGLDisplay display = eglDisplay(); if (display == EGL_NO_DISPLAY) { if (!hasClientExtension(QByteArrayLiteral("EGL_EXT_device_base")) && !(hasClientExtension(QByteArrayLiteral("EGL_EXT_device_query")) && @@ -138,7 +139,7 @@ bool EglStreamBackend::initializeEgl() eglQueryDevicesEXT(numDevices, devices.data(), &numDevices); for (EGLDeviceEXT device : devices) { const char *drmDeviceFile = eglQueryDeviceStringEXT(device, EGL_DRM_DEVICE_FILE_EXT); - if (m_backend->devNode() != drmDeviceFile) { + if (m_gpu->devNode().compare(drmDeviceFile)) { continue; } @@ -150,7 +151,7 @@ bool EglStreamBackend::initializeEgl() } EGLint platformAttribs[] = { - EGL_DRM_MASTER_FD_EXT, m_backend->fd(), + EGL_DRM_MASTER_FD_EXT, m_gpu->fd(), EGL_NONE }; display = eglGetPlatformDisplayEXT(EGL_PLATFORM_DEVICE_EXT, device, platformAttribs); @@ -254,7 +255,7 @@ void EglStreamBackend::attachStreamConsumer(KWaylandServer::SurfaceInterface *su void EglStreamBackend::init() { - if (!m_backend->atomicModeSetting()) { + if (!m_gpu->atomicModeSetting()) { setFailed("EGLStream backend requires atomic modesetting"); return; } @@ -307,7 +308,7 @@ bool EglStreamBackend::resetOutput(Output &o, DrmOutput *drmOutput) delete o.buffer; } // dumb buffer used for modesetting - o.buffer = m_backend->createBuffer(drmOutput->pixelSize()); + o.buffer = m_gpu->createBuffer(drmOutput->pixelSize()); EGLAttrib streamAttribs[] = { EGL_STREAM_FIFO_LENGTH_KHR, 0, // mailbox mode diff --git a/plugins/platforms/drm/egl_stream_backend.h b/plugins/platforms/drm/egl_stream_backend.h index c72eb77a0..9d99bc743 100644 --- a/plugins/platforms/drm/egl_stream_backend.h +++ b/plugins/platforms/drm/egl_stream_backend.h @@ -19,6 +19,7 @@ namespace KWin class DrmBackend; class DrmOutput; class DrmBuffer; +class DrmGpu; /** * @brief OpenGL Backend using Egl with an EGLDevice. @@ -27,7 +28,7 @@ class EglStreamBackend : public AbstractEglBackend { Q_OBJECT public: - EglStreamBackend(DrmBackend *b); + EglStreamBackend(DrmBackend *b, DrmGpu *gpu); ~EglStreamBackend() override; void screenGeometryChanged(const QSize &size) override; SceneOpenGLTexturePrivate *createBackendTexture(SceneOpenGLTexture *texture) override; @@ -70,6 +71,7 @@ private: void createOutput(DrmOutput *output); DrmBackend *m_backend; + DrmGpu *m_gpu; QVector m_outputs; KWaylandServer::EglStreamControllerInterface *m_eglStreamControllerInterface; QHash m_streamTextures; diff --git a/plugins/platforms/drm/scene_qpainter_drm_backend.cpp b/plugins/platforms/drm/scene_qpainter_drm_backend.cpp index b3d8fd297..09ae03810 100644 --- a/plugins/platforms/drm/scene_qpainter_drm_backend.cpp +++ b/plugins/platforms/drm/scene_qpainter_drm_backend.cpp @@ -10,21 +10,23 @@ #include "drm_backend.h" #include "drm_output.h" #include "logind.h" +#include "drm_gpu.h" namespace KWin { -DrmQPainterBackend::DrmQPainterBackend(DrmBackend *backend) +DrmQPainterBackend::DrmQPainterBackend(DrmBackend *backend, DrmGpu *gpu) : QObject() , QPainterBackend() , m_backend(backend) + , m_gpu(gpu) { const auto outputs = m_backend->drmOutputs(); for (auto output: outputs) { initOutput(output); } - connect(m_backend, &DrmBackend::outputAdded, this, &DrmQPainterBackend::initOutput); - connect(m_backend, &DrmBackend::outputRemoved, this, + connect(m_gpu, &DrmGpu::outputAdded, this, &DrmQPainterBackend::initOutput); + connect(m_gpu, &DrmGpu::outputDisabled, this, [this] (DrmOutput *o) { auto it = std::find_if(m_outputs.begin(), m_outputs.end(), [o] (const Output &output) { @@ -53,7 +55,7 @@ void DrmQPainterBackend::initOutput(DrmOutput *output) { Output o; auto initBuffer = [&o, output, this] (int index) { - o.buffer[index] = m_backend->createBuffer(output->pixelSize()); + o.buffer[index] = m_gpu->createBuffer(output->pixelSize()); if (o.buffer[index]->map()) { o.buffer[index]->image()->fill(Qt::black); } @@ -71,7 +73,7 @@ void DrmQPainterBackend::initOutput(DrmOutput *output) delete (*it).buffer[0]; delete (*it).buffer[1]; auto initBuffer = [it, output, this] (int index) { - it->buffer[index] = m_backend->createBuffer(output->pixelSize()); + it->buffer[index] = m_gpu->createBuffer(output->pixelSize()); if (it->buffer[index]->map()) { it->buffer[index]->image()->fill(Qt::black); } diff --git a/plugins/platforms/drm/scene_qpainter_drm_backend.h b/plugins/platforms/drm/scene_qpainter_drm_backend.h index f201e420c..58c7d2d67 100644 --- a/plugins/platforms/drm/scene_qpainter_drm_backend.h +++ b/plugins/platforms/drm/scene_qpainter_drm_backend.h @@ -18,12 +18,13 @@ namespace KWin class DrmBackend; class DrmDumbBuffer; class DrmOutput; +class DrmGpu; class DrmQPainterBackend : public QObject, public QPainterBackend { Q_OBJECT public: - DrmQPainterBackend(DrmBackend *backend); + DrmQPainterBackend(DrmBackend *backend, DrmGpu *gpu); ~DrmQPainterBackend() override; QImage *buffer() override; @@ -43,6 +44,7 @@ private: }; QVector m_outputs; DrmBackend *m_backend; + DrmGpu *m_gpu; }; } diff --git a/plugins/platforms/fbdev/fb_backend.cpp b/plugins/platforms/fbdev/fb_backend.cpp index c74335cdf..ce8012bd0 100644 --- a/plugins/platforms/fbdev/fb_backend.cpp +++ b/plugins/platforms/fbdev/fb_backend.cpp @@ -90,7 +90,7 @@ void FramebufferBackend::openFrameBuffer() VirtualTerminal::self()->init(); QString framebufferDevice = deviceIdentifier().constData(); if (framebufferDevice.isEmpty()) { - framebufferDevice = QString(Udev().primaryFramebuffer()->devNode()); + framebufferDevice = QString(Udev().listFramebuffers().at(0)->devNode()); } int fd = LogindIntegration::self()->takeDevice(framebufferDevice.toUtf8().constData()); qCDebug(KWIN_FB) << "Using frame buffer device:" << framebufferDevice; diff --git a/udev.cpp b/udev.cpp index aa8b7b462..f6a9e63ac 100644 --- a/udev.cpp +++ b/udev.cpp @@ -42,7 +42,7 @@ public: }; void addMatch(Match match, const char *name); void scan(); - UdevDevice::Ptr find(std::function test); + std::vector find(); private: Udev *m_udev; @@ -91,14 +91,15 @@ void UdevEnumerate::scan() udev_enumerate_scan_devices(m_enumerate.data()); } -UdevDevice::Ptr UdevEnumerate::find(std::function test) +std::vector UdevEnumerate::find() { + std::vector vect; if (m_enumerate.isNull()) { - return UdevDevice::Ptr(); + vect.push_back( UdevDevice::Ptr() ); + return vect; } QString defaultSeat = QStringLiteral("seat0"); udev_list_entry *it = udev_enumerate_get_list_entry(m_enumerate.data()); - UdevDevice::Ptr firstFound; while (it) { auto current = it; it = udev_list_entry_get_next(it); @@ -113,62 +114,76 @@ UdevDevice::Ptr UdevEnumerate::find(std::functionseat()) { continue; } - if (test(device)) { - return device; - } - if (!firstFound) { - firstFound.swap(device); - } + if (device->getParentWithSubsystemDevType("pci")) + vect.push_back(std::move(device)); } - return firstFound; + return vect; } -UdevDevice::Ptr Udev::primaryGpu() +std::vector Udev::listGPUs() { if (!m_udev) { - return UdevDevice::Ptr(); + std::vector vect; + vect.push_back(UdevDevice::Ptr()); + return vect; } #if defined(Q_OS_FREEBSD) - return deviceFromSyspath("/dev/dri/card0"); + std::vector r; + r.push_back(deviceFromSyspath("/dev/dri/card0")); + return r; #else UdevEnumerate enumerate(this); enumerate.addMatch(UdevEnumerate::Match::SubSystem, "drm"); - enumerate.addMatch(UdevEnumerate::Match::SysName, "card[0-9]*"); + enumerate.addMatch(UdevEnumerate::Match::SysName, "card[0-9]"); enumerate.scan(); - return enumerate.find([](const UdevDevice::Ptr &device) { - auto pci = device->getParentWithSubsystemDevType("pci"); - if (!pci) { - return false; - } - const char *systAttrValue = udev_device_get_sysattr_value(pci, "boot_vga"); + auto vect = enumerate.find(); + std::sort(vect.begin(), vect.end(), [](const UdevDevice::Ptr &device1, const UdevDevice::Ptr &device2) { + auto pci1 = device1->getParentWithSubsystemDevType("pci"); + auto pci2 = device2->getParentWithSubsystemDevType("pci"); + // if set as boot GPU, prefer 1 + const char *systAttrValue = udev_device_get_sysattr_value(pci1, "boot_vga"); if (systAttrValue && qstrcmp(systAttrValue, "1") == 0) { return true; } - return false; + // if set as boot GPU, prefer 2 + systAttrValue = udev_device_get_sysattr_value(pci2, "boot_vga"); + if (systAttrValue && qstrcmp(systAttrValue, "1") == 0) { + return false; + } + return udev_device_get_sysnum(pci1) > udev_device_get_sysnum(pci2); }); + return vect; #endif } -UdevDevice::Ptr Udev::primaryFramebuffer() +std::vector Udev::listFramebuffers() { if (!m_udev) { - return UdevDevice::Ptr(); + std::vector vect; + vect.push_back(UdevDevice::Ptr()); + return vect; } UdevEnumerate enumerate(this); enumerate.addMatch(UdevEnumerate::Match::SubSystem, "graphics"); - enumerate.addMatch(UdevEnumerate::Match::SysName, "fb[0-9]*"); + enumerate.addMatch(UdevEnumerate::Match::SysName, "fb[0-9]"); enumerate.scan(); - return enumerate.find([](const UdevDevice::Ptr &device) { - auto pci = device->getParentWithSubsystemDevType("pci"); - if (!pci) { - return false; - } - const char *systAttrValue = udev_device_get_sysattr_value(pci, "boot_vga"); + auto vect = enumerate.find(); + std::sort(vect.begin(), vect.end(), [](const UdevDevice::Ptr &device1, const UdevDevice::Ptr &device2) { + auto pci1 = device1->getParentWithSubsystemDevType("pci"); + auto pci2 = device2->getParentWithSubsystemDevType("pci"); + // if set as boot GPU, prefer 1 + const char *systAttrValue = udev_device_get_sysattr_value(pci1, "boot_vga"); if (systAttrValue && qstrcmp(systAttrValue, "1") == 0) { return true; } - return false; + // if set as boot GPU, prefer 2 + systAttrValue = udev_device_get_sysattr_value(pci2, "boot_vga"); + if (systAttrValue && qstrcmp(systAttrValue, "1") == 0) { + return false; + } + return udev_device_get_sysnum(pci1) > udev_device_get_sysnum(pci2); }); + return vect; } UdevDevice::Ptr Udev::deviceFromSyspath(const char *syspath) diff --git a/udev.h b/udev.h index fc7cf11d2..777a27c9f 100644 --- a/udev.h +++ b/udev.h @@ -11,6 +11,9 @@ #include #include +#include +#include + struct udev; struct udev_device; struct udev_monitor; @@ -70,8 +73,8 @@ public: bool isValid() const { return m_udev != nullptr; } - UdevDevice::Ptr primaryGpu(); - UdevDevice::Ptr primaryFramebuffer(); + std::vector listGPUs(); + std::vector listFramebuffers(); UdevDevice::Ptr deviceFromSyspath(const char *syspath); UdevMonitor *monitor(); operator udev*() const {