diff --git a/platformsupport/scenes/opengl/abstract_egl_backend.cpp b/platformsupport/scenes/opengl/abstract_egl_backend.cpp index cd5c9344da..be690bb121 100644 --- a/platformsupport/scenes/opengl/abstract_egl_backend.cpp +++ b/platformsupport/scenes/opengl/abstract_egl_backend.cpp @@ -89,10 +89,15 @@ static void destroyGlobalShareContext() kwinApp()->platform()->setSceneEglGlobalShareContext(EGL_NO_CONTEXT); } +AbstractEglBackend *AbstractEglBackend::s_primaryBackend = nullptr; + AbstractEglBackend::AbstractEglBackend() : QObject(nullptr) , OpenGLBackend() { + if (s_primaryBackend == nullptr) { + setPrimaryBackend(this); + } connect(Compositor::self(), &Compositor::aboutToDestroy, this, &AbstractEglBackend::teardown); } @@ -346,19 +351,25 @@ bool AbstractEglBackend::createContext() return false; } m_context = ctx; - kwinApp()->platform()->setSceneEglContext(m_context); + if (isPrimary()) { + kwinApp()->platform()->setSceneEglContext(m_context); + } return true; } void AbstractEglBackend::setEglDisplay(const EGLDisplay &display) { m_display = display; - kwinApp()->platform()->setSceneEglDisplay(display); + if (isPrimary()) { + kwinApp()->platform()->setSceneEglDisplay(display); + } } void AbstractEglBackend::setConfig(const EGLConfig &config) { m_config = config; - kwinApp()->platform()->setSceneEglConfig(config); + if (isPrimary()) { + kwinApp()->platform()->setSceneEglConfig(config); + } } void AbstractEglBackend::setSurface(const EGLSurface &surface) diff --git a/platformsupport/scenes/opengl/abstract_egl_backend.h b/platformsupport/scenes/opengl/abstract_egl_backend.h index d8137f39f5..b72c362317 100644 --- a/platformsupport/scenes/opengl/abstract_egl_backend.h +++ b/platformsupport/scenes/opengl/abstract_egl_backend.h @@ -51,6 +51,17 @@ public: QSharedPointer textureForOutput(AbstractOutput *output) const override; + static void setPrimaryBackend(AbstractEglBackend *primaryBackend) { + s_primaryBackend = primaryBackend; + } + static AbstractEglBackend *primaryBackend() { + return s_primaryBackend; + } + + bool isPrimary() const { + return this == s_primaryBackend; + } + protected: AbstractEglBackend(); void setEglDisplay(const EGLDisplay &display); @@ -65,7 +76,6 @@ protected: void initWayland(); bool hasClientExtension(const QByteArray &ext) const; bool isOpenGLES() const; - bool createContext(); private: @@ -75,8 +85,11 @@ private: EGLSurface m_surface = EGL_NO_SURFACE; EGLContext m_context = EGL_NO_CONTEXT; EGLConfig m_config = nullptr; - QList m_clientExtensions; + // note: m_dmaBuf is nullptr if this is not the primary backend EglDmabuf *m_dmaBuf = nullptr; + QList m_clientExtensions; + + static AbstractEglBackend * s_primaryBackend; }; class KWIN_EXPORT AbstractEglTexture : public SceneOpenGLTexturePrivate diff --git a/plugins/platforms/drm/CMakeLists.txt b/plugins/platforms/drm/CMakeLists.txt index 091ca287b5..aa2b0c9a0c 100644 --- a/plugins/platforms/drm/CMakeLists.txt +++ b/plugins/platforms/drm/CMakeLists.txt @@ -12,6 +12,8 @@ set(DRM_SOURCES scene_qpainter_drm_backend.cpp screens_drm.cpp drm_gpu.cpp + egl_multi_backend.cpp + abstract_egl_drm_backend.cpp ) if (HAVE_GBM) diff --git a/plugins/platforms/drm/abstract_egl_drm_backend.cpp b/plugins/platforms/drm/abstract_egl_drm_backend.cpp new file mode 100644 index 0000000000..cf4f6900a5 --- /dev/null +++ b/plugins/platforms/drm/abstract_egl_drm_backend.cpp @@ -0,0 +1,43 @@ +/* + * 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 "abstract_egl_drm_backend.h" + +#include "drm_backend.h" +#include "drm_gpu.h" + +using namespace KWin; + +AbstractEglDrmBackend::AbstractEglDrmBackend(DrmBackend *drmBackend, DrmGpu *gpu) : m_backend(drmBackend), m_gpu(gpu) +{ + m_gpu->setEglBackend(this); + // Egl is always direct rendering. + setIsDirectRendering(true); + setSyncsToVBlank(true); +} + +AbstractEglDrmBackend::~AbstractEglDrmBackend() +{ + cleanup(); +} + +void AbstractEglDrmBackend::screenGeometryChanged(const QSize &size) +{ + Q_UNUSED(size) +} + +bool AbstractEglDrmBackend::usesOverlayWindow() const +{ + return false; +} + +bool AbstractEglDrmBackend::perScreenRendering() const +{ + return true; +} diff --git a/plugins/platforms/drm/abstract_egl_drm_backend.h b/plugins/platforms/drm/abstract_egl_drm_backend.h new file mode 100644 index 0000000000..aa229e23f2 --- /dev/null +++ b/plugins/platforms/drm/abstract_egl_drm_backend.h @@ -0,0 +1,68 @@ +/* + * 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 KWIN_ABSTRACTEGLDRMBACKEND_H +#define KWIN_ABSTRACTEGLDRMBACKEND_H + +#include "abstract_egl_backend.h" + +namespace KWin +{ + +class DrmBackend; +class DrmGpu; + +class AbstractEglDrmBackend : public AbstractEglBackend +{ +public: + ~AbstractEglDrmBackend(); + + bool usesOverlayWindow() const override; + bool perScreenRendering() const override; + void screenGeometryChanged(const QSize &size) override; + + virtual int screenCount() const = 0; + virtual void addSecondaryGpuOutput(AbstractOutput *output) { + Q_UNUSED(output) + } + virtual int getDmabufForSecondaryGpuOutput(AbstractOutput *output, uint32_t *format, uint32_t *stride) { + Q_UNUSED(output) + Q_UNUSED(format) + Q_UNUSED(stride) + return 0; + } + virtual void cleanupDmabufForSecondaryGpuOutput(AbstractOutput *output) { + Q_UNUSED(output) + } + virtual void removeSecondaryGpuOutput(AbstractOutput *output) { + Q_UNUSED(output) + } + virtual QRegion beginFrameForSecondaryGpu(AbstractOutput *output) { + Q_UNUSED(output) + return QRegion(); + } + virtual void renderFramebufferToSurface(AbstractOutput *output) { + Q_UNUSED(output) + } + + static AbstractEglDrmBackend *renderingBackend() { + return static_cast(primaryBackend()); + } + +protected: + AbstractEglDrmBackend(DrmBackend *drmBackend, DrmGpu *gpu); + + DrmBackend *m_backend; + DrmGpu *m_gpu; + +}; + +} + +#endif // KWIN_ABSTRACTEGLDRMBACKEND_H diff --git a/plugins/platforms/drm/drm_backend.cpp b/plugins/platforms/drm/drm_backend.cpp index 43dd7d1d00..7210b6092a 100644 --- a/plugins/platforms/drm/drm_backend.cpp +++ b/plugins/platforms/drm/drm_backend.cpp @@ -47,6 +47,7 @@ #include #include "drm_gpu.h" +#include "egl_multi_backend.h" #ifndef DRM_CAP_CURSOR_WIDTH #define DRM_CAP_CURSOR_WIDTH 0x8 @@ -274,8 +275,15 @@ void DrmBackend::openDrm() 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; + if (gpu->useEglStreams()) { + // TODO this needs to be removed once EglStreamBackend supports multi-gpu operation + if (gpu_index == 0) { + m_gpus.append(gpu); + break; + } + } else { + m_gpus.append(gpu); + } } // trying to activate Atomic Mode Setting (this means also Universal Planes) @@ -349,8 +357,9 @@ bool DrmBackend::updateOutputs() return false; } const auto oldOutputs = m_outputs; - for (auto gpu : m_gpus) + 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(); }); if (oldOutputs != m_outputs) { @@ -623,12 +632,21 @@ OpenGLBackend *DrmBackend::createOpenGLBackend() { #if HAVE_EGL_STREAMS if (m_gpus.at(0)->useEglStreams()) { - return new EglStreamBackend(this, m_gpus.at(0)); + auto backend = new EglStreamBackend(this, m_gpus.at(0)); + AbstractEglBackend::setPrimaryBackend(backend); + return backend; } #endif #if HAVE_GBM - return new EglGbmBackend(this, m_gpus.at(0)); + auto backend0 = new EglGbmBackend(this, m_gpus.at(0)); + AbstractEglBackend::setPrimaryBackend(backend0); + EglMultiBackend *backend = new EglMultiBackend(backend0); + for (int i = 1; i < m_gpus.count(); i++) { + auto backendi = new EglGbmBackend(this, m_gpus.at(i)); + backend->addBackend(backendi); + } + return backend; #else return Platform::createOpenGLBackend(); #endif @@ -678,9 +696,9 @@ QString DrmBackend::supportInformation() const DmaBufTexture *DrmBackend::createDmaBufTexture(const QSize &size) { #if HAVE_GBM - // 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 + // make sure we're on the right context: + m_gpus.at(0)->eglBackend()->makeCurrent(); return GbmDmaBuf::createBuffer(size, m_gpus.at(0)->gbmDevice()); #else return nullptr; diff --git a/plugins/platforms/drm/drm_buffer_gbm.cpp b/plugins/platforms/drm/drm_buffer_gbm.cpp index 1bf3c7ab99..4d4a9af73d 100644 --- a/plugins/platforms/drm/drm_buffer_gbm.cpp +++ b/plugins/platforms/drm/drm_buffer_gbm.cpp @@ -41,6 +41,17 @@ DrmSurfaceBuffer::DrmSurfaceBuffer(int fd, const std::shared_ptr &su gbm_bo_set_user_data(m_bo, this, nullptr); } +DrmSurfaceBuffer::DrmSurfaceBuffer(int fd, gbm_bo *buffer) + : DrmBuffer(fd) + , m_bo(buffer) +{ + m_size = QSize(gbm_bo_get_width(m_bo), gbm_bo_get_height(m_bo)); + if (drmModeAddFB(fd, m_size.width(), m_size.height(), 24, 32, gbm_bo_get_stride(m_bo), gbm_bo_get_handle(m_bo).u32, &m_bufferId) != 0) { + qCWarning(KWIN_DRM) << "drmModeAddFB failed"; + } + gbm_bo_set_user_data(m_bo, this, nullptr); +} + DrmSurfaceBuffer::~DrmSurfaceBuffer() { if (m_bufferId) { @@ -51,7 +62,11 @@ DrmSurfaceBuffer::~DrmSurfaceBuffer() void DrmSurfaceBuffer::releaseGbm() { - m_surface->releaseBuffer(m_bo); + if (m_surface) { + m_surface->releaseBuffer(m_bo); + } else if (m_bo) { + gbm_bo_destroy(m_bo); + } m_bo = nullptr; } diff --git a/plugins/platforms/drm/drm_buffer_gbm.h b/plugins/platforms/drm/drm_buffer_gbm.h index d27b3031fe..be815085db 100644 --- a/plugins/platforms/drm/drm_buffer_gbm.h +++ b/plugins/platforms/drm/drm_buffer_gbm.h @@ -25,6 +25,7 @@ class DrmSurfaceBuffer : public DrmBuffer { public: DrmSurfaceBuffer(int fd, const std::shared_ptr &surface); + DrmSurfaceBuffer(int fd, gbm_bo *buffer); ~DrmSurfaceBuffer() override; bool needsModeChange(DrmBuffer *b) const override { diff --git a/plugins/platforms/drm/drm_gpu.cpp b/plugins/platforms/drm/drm_gpu.cpp index d21e3031df..25a2063551 100644 --- a/plugins/platforms/drm/drm_gpu.cpp +++ b/plugins/platforms/drm/drm_gpu.cpp @@ -256,7 +256,7 @@ bool DrmGpu::updateOutputs() if (!output->initCursor(m_cursorSize)) { m_backend->setSoftwareCursorForced(true); } - qCDebug(KWIN_DRM) << "Found new output with uuid" << output->uuid(); + qCDebug(KWIN_DRM) << "Found new output with uuid" << output->uuid() << "on gpu" << m_devNode; connectedOutputs << output; emit outputAdded(output); diff --git a/plugins/platforms/drm/drm_gpu.h b/plugins/platforms/drm/drm_gpu.h index e7b494579c..078c87e8d8 100644 --- a/plugins/platforms/drm/drm_gpu.h +++ b/plugins/platforms/drm/drm_gpu.h @@ -78,6 +78,10 @@ public: return m_planes; } + AbstractEglBackend *eglBackend() { + return m_eglBackend; + } + void setGbmDevice(gbm_device *d) { m_gbmDevice = d; } @@ -94,6 +98,10 @@ public: return new DrmDumbBuffer(m_fd, size); } + void setEglBackend(AbstractEglBackend *eglBackend) { + m_eglBackend = eglBackend; + } + Q_SIGNALS: void outputAdded(DrmOutput *output); void outputRemoved(DrmOutput *output); @@ -110,6 +118,7 @@ private: DrmOutput *findOutput(quint32 connector); DrmBackend* const m_backend; + AbstractEglBackend *m_eglBackend; const QByteArray m_devNode; QSize m_cursorSize; diff --git a/plugins/platforms/drm/egl_gbm_backend.cpp b/plugins/platforms/drm/egl_gbm_backend.cpp index c3a5d0913d..f51f85be9f 100644 --- a/plugins/platforms/drm/egl_gbm_backend.cpp +++ b/plugins/platforms/drm/egl_gbm_backend.cpp @@ -21,27 +21,18 @@ #include // system #include +#include namespace KWin { EglGbmBackend::EglGbmBackend(DrmBackend *drmBackend, DrmGpu *gpu) - : AbstractEglBackend() - , m_backend(drmBackend) - , m_gpu(gpu) + : AbstractEglDrmBackend(drmBackend, gpu) { - // Egl is always direct rendering. - setIsDirectRendering(true); - setSyncsToVBlank(true); connect(m_gpu, &DrmGpu::outputEnabled, this, &EglGbmBackend::createOutput); connect(m_gpu, &DrmGpu::outputDisabled, this, &EglGbmBackend::removeOutput); } -EglGbmBackend::~EglGbmBackend() -{ - cleanup(); -} - void EglGbmBackend::cleanupSurfaces() { for (auto it = m_outputs.begin(); it != m_outputs.end(); ++it) { @@ -55,6 +46,7 @@ void EglGbmBackend::cleanupFramebuffer(Output &output) if (!output.render.framebuffer) { return; } + makeContextCurrent(output); glDeleteTextures(1, &output.render.texture); output.render.texture = 0; glDeleteFramebuffers(1, &output.render.framebuffer); @@ -69,6 +61,15 @@ void EglGbmBackend::cleanupOutput(Output &output) if (output.eglSurface != EGL_NO_SURFACE) { eglDestroySurface(eglDisplay(), output.eglSurface); } + if (output.secondaryGbmBo) { + output.gbmSurface.get()->releaseBuffer(output.secondaryGbmBo); + } + if (output.importedGbmBo) { + gbm_bo_destroy(output.importedGbmBo); + } + if (output.dmabufFd) { + close(output.dmabufFd); + } } bool EglGbmBackend::initializeEgl() @@ -118,42 +119,52 @@ void EglGbmBackend::init() setFailed("Could not initialize rendering context"); return; } - - initKWinGL(); initBufferAge(); - initWayland(); + // at the moment: no secondary GPU -> no OpenGL context! + if (isPrimary()) { + initKWinGL(); + initWayland(); + } } bool EglGbmBackend::initRenderingContext() { initBufferConfigs(); - if (!createContext()) { + // no secondary GPU -> no OpenGL context! + if (isPrimary() && !createContext()) { return false; } - const auto outputs = m_backend->drmOutputs(); + const auto outputs = m_gpu->outputs(); for (DrmOutput *drmOutput: outputs) { createOutput(drmOutput); } - if (m_outputs.isEmpty()) { + if (m_outputs.isEmpty() && !outputs.isEmpty()) { qCCritical(KWIN_DRM) << "Create Window Surfaces failed"; return false; } - - // Set our first surface as the one for the abstract backend, just to make it happy. - setSurface(m_outputs.first().eglSurface); - - return makeContextCurrent(m_outputs.first()); + if (!m_outputs.isEmpty()) { + // Set our first surface as the one for the abstract backend, just to make it happy. + setSurface(m_outputs.first().eglSurface); + if (isPrimary()) { + return makeContextCurrent(m_outputs.first()); + } + } + return true; } -std::shared_ptr EglGbmBackend::createGbmSurface(const QSize &size) const +std::shared_ptr EglGbmBackend::createGbmSurface(const QSize &size, const bool linear) const { + auto flags = GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING; + if (linear) { + flags |= GBM_BO_USE_LINEAR; + } auto gbmSurface = std::make_shared(m_gpu->gbmDevice(), size.width(), size.height(), GBM_FORMAT_XRGB8888, - GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); + flags); if (!gbmSurface) { qCCritical(KWIN_DRM) << "Creating GBM surface failed"; return nullptr; @@ -178,7 +189,7 @@ bool EglGbmBackend::resetOutput(Output &output, DrmOutput *drmOutput) const QSize size = drmOutput->hardwareTransforms() ? drmOutput->pixelSize() : drmOutput->modeSize(); - auto gbmSurface = createGbmSurface(size); + auto gbmSurface = createGbmSurface(size, output.onSecondaryGPU); if (!gbmSurface) { return false; } @@ -187,11 +198,11 @@ bool EglGbmBackend::resetOutput(Output &output, DrmOutput *drmOutput) return false; } + if (surface() == output.eglSurface || surface() == EGL_NO_SURFACE) { + setSurface(eglSurface); + } // destroy previous surface if (output.eglSurface != EGL_NO_SURFACE) { - if (surface() == output.eglSurface) { - setSurface(eglSurface); - } eglDestroySurface(eglDisplay(), output.eglSurface); } output.eglSurface = eglSurface; @@ -203,21 +214,28 @@ bool EglGbmBackend::resetOutput(Output &output, DrmOutput *drmOutput) void EglGbmBackend::createOutput(DrmOutput *drmOutput) { - Output newOutput; - if (resetOutput(newOutput, drmOutput)) { - connect(drmOutput, &DrmOutput::modeChanged, this, - [drmOutput, this] { - auto it = std::find_if(m_outputs.begin(), m_outputs.end(), - [drmOutput] (const auto &output) { - return output.output == drmOutput; + if (isPrimary()) { + Output newOutput; + if (resetOutput(newOutput, drmOutput)) { + connect(drmOutput, &DrmOutput::modeChanged, this, + [drmOutput, this] { + auto it = std::find_if(m_outputs.begin(), m_outputs.end(), + [drmOutput] (const auto &output) { + return output.output == drmOutput; + } + ); + if (it == m_outputs.end()) { + return; } - ); - if (it == m_outputs.end()) { - return; + resetOutput(*it, drmOutput); } - resetOutput(*it, drmOutput); - } - ); + ); + m_outputs << newOutput; + } + } else { + Output newOutput; + newOutput.output = drmOutput; + renderingBackend()->addSecondaryGpuOutput(drmOutput); m_outputs << newOutput; } } @@ -232,11 +250,129 @@ void EglGbmBackend::removeOutput(DrmOutput *drmOutput) if (it == m_outputs.end()) { return; } - - cleanupOutput(*it); + if (this != primaryBackend()) { + renderingBackend()->removeSecondaryGpuOutput((*it).output); + } else { + cleanupOutput(*it); + } m_outputs.erase(it); } +void EglGbmBackend::addSecondaryGpuOutput(AbstractOutput *output) +{ + DrmOutput *drmOutput = static_cast(output); + Output newOutput; + newOutput.onSecondaryGPU = true; + if (resetOutput(newOutput, drmOutput)) { + connect(drmOutput, &DrmOutput::modeChanged, this, + [drmOutput, this] { + auto it = std::find_if(m_secondaryGpuOutputs.begin(), m_secondaryGpuOutputs.end(), + [drmOutput] (const auto &output) { + return output.output == drmOutput; + } + ); + if (it == m_secondaryGpuOutputs.end()) { + return; + } + resetOutput(*it, drmOutput); + } + ); + m_secondaryGpuOutputs << newOutput; + } +} + +int EglGbmBackend::getDmabufForSecondaryGpuOutput(AbstractOutput *output, uint32_t *format, uint32_t *stride) +{ + DrmOutput *drmOutput = static_cast(output); + auto it = std::find_if(m_secondaryGpuOutputs.begin(), m_secondaryGpuOutputs.end(), + [drmOutput] (const Output &output) { + return output.output == drmOutput; + } + ); + if (it == m_secondaryGpuOutputs.end()) { + return -1; + } + auto error = eglSwapBuffers(eglDisplay(), it->eglSurface); + if (error != EGL_TRUE) { + qCDebug(KWIN_DRM) << "an error occurred while swapping buffers" << error; + return -1; + } + it->secondaryGbmBo = it->gbmSurface->lockFrontBuffer(); + int fd = gbm_bo_get_fd(it->secondaryGbmBo); + if (fd == -1) { + qCDebug(KWIN_DRM) << "failed to export gbm_bo as dma-buf!"; + return -1; + } + it->dmabufFd = fd; + *format = gbm_bo_get_format(it->secondaryGbmBo); + *stride = gbm_bo_get_stride(it->secondaryGbmBo); + return it->dmabufFd; +} + +void EglGbmBackend::cleanupDmabufForSecondaryGpuOutput(AbstractOutput *output) +{ + DrmOutput *drmOutput = static_cast(output); + auto it = std::find_if(m_secondaryGpuOutputs.begin(), m_secondaryGpuOutputs.end(), + [drmOutput] (const Output &output) { + return output.output == drmOutput; + } + ); + if (it == m_secondaryGpuOutputs.end()) { + return; + } + if (it->dmabufFd) { + close(it->dmabufFd); + it->dmabufFd = 0; + } + if (it->secondaryGbmBo) { + it->gbmSurface.get()->releaseBuffer(it->secondaryGbmBo); + it->secondaryGbmBo = nullptr; + } +} + +void EglGbmBackend::removeSecondaryGpuOutput(AbstractOutput *output) +{ + DrmOutput *drmOutput = static_cast(output); + auto it = std::find_if(m_secondaryGpuOutputs.begin(), m_secondaryGpuOutputs.end(), + [drmOutput] (const Output &output) { + return output.output == drmOutput; + } + ); + if (it == m_secondaryGpuOutputs.end()) { + return; + } + cleanupOutput(*it); + m_secondaryGpuOutputs.erase(it); +} + +QRegion EglGbmBackend::beginFrameForSecondaryGpu(AbstractOutput *output) +{ + DrmOutput *drmOutput = static_cast(output); + auto it = std::find_if(m_secondaryGpuOutputs.begin(), m_secondaryGpuOutputs.end(), + [drmOutput] (const Output &output) { + return output.output == drmOutput; + } + ); + if (it == m_secondaryGpuOutputs.end()) { + return QRegion(); + } + return prepareRenderingForOutput(*it); +} + +void EglGbmBackend::renderFramebufferToSurface(AbstractOutput *output) +{ + DrmOutput *drmOutput = static_cast(output); + auto it = std::find_if(m_secondaryGpuOutputs.begin(), m_secondaryGpuOutputs.end(), + [drmOutput] (const Output &output) { + return output.output == drmOutput; + } + ); + if (it == m_secondaryGpuOutputs.end()) { + return; + } + renderFramebufferToSurface(*it); +} + const float vertices[] = { -1.0f, 1.0f, -1.0f, -1.0f, @@ -259,15 +395,15 @@ const float texCoords[] = { bool EglGbmBackend::resetFramebuffer(Output &output) { - cleanupFramebuffer(output); - - if (output.output->hardwareTransforms()) { + if (output.output->hardwareTransforms() && !output.onSecondaryGPU) { // No need for an extra render target. return true; } makeContextCurrent(output); + cleanupFramebuffer(output); + glGenFramebuffers(1, &output.render.framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, output.render.framebuffer); GLRenderTarget::setKWinFramebuffer(output.render.framebuffer); @@ -278,7 +414,7 @@ bool EglGbmBackend::resetFramebuffer(Output &output) const QSize texSize = output.output->pixelSize(); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texSize.width(), texSize.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glBindTexture(GL_TEXTURE_2D, 0); @@ -293,6 +429,7 @@ bool EglGbmBackend::resetFramebuffer(Output &output) glBindFramebuffer(GL_FRAMEBUFFER, 0); GLRenderTarget::setKWinFramebuffer(0); + return true; } @@ -309,56 +446,83 @@ void EglGbmBackend::initRenderTarget(Output &output) void EglGbmBackend::renderFramebufferToSurface(Output &output) { - if (!output.render.framebuffer) { + if (!output.render.framebuffer && isPrimary()) { // No additional render target. return; } - initRenderTarget(output); - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - GLRenderTarget::setKWinFramebuffer(0); - const auto size = output.output->modeSize(); - glViewport(0, 0, size.width(), size.height()); + if (isPrimary()) { + // primary GPU + makeContextCurrent(output); - auto shader = ShaderManager::instance()->pushShader(ShaderTrait::MapTexture); + glViewport(0, 0, size.width(), size.height()); - QMatrix4x4 mvpMatrix; + auto shader = ShaderManager::instance()->pushShader(ShaderTrait::MapTexture); - const DrmOutput *drmOutput = output.output; - switch (drmOutput->transform()) { - case DrmOutput::Transform::Normal: - case DrmOutput::Transform::Flipped: - break; - case DrmOutput::Transform::Rotated90: - case DrmOutput::Transform::Flipped90: - mvpMatrix.rotate(90, 0, 0, 1); - break; - case DrmOutput::Transform::Rotated180: - case DrmOutput::Transform::Flipped180: - mvpMatrix.rotate(180, 0, 0, 1); - break; - case DrmOutput::Transform::Rotated270: - case DrmOutput::Transform::Flipped270: - mvpMatrix.rotate(270, 0, 0, 1); - break; + QMatrix4x4 mvpMatrix; + + const DrmOutput *drmOutput = output.output; + switch (drmOutput->transform()) { + case DrmOutput::Transform::Normal: + case DrmOutput::Transform::Flipped: + break; + case DrmOutput::Transform::Rotated90: + case DrmOutput::Transform::Flipped90: + mvpMatrix.rotate(90, 0, 0, 1); + break; + case DrmOutput::Transform::Rotated180: + case DrmOutput::Transform::Flipped180: + mvpMatrix.rotate(180, 0, 0, 1); + break; + case DrmOutput::Transform::Rotated270: + case DrmOutput::Transform::Flipped270: + mvpMatrix.rotate(270, 0, 0, 1); + break; + } + switch (drmOutput->transform()) { + case DrmOutput::Transform::Flipped: + case DrmOutput::Transform::Flipped90: + case DrmOutput::Transform::Flipped180: + case DrmOutput::Transform::Flipped270: + mvpMatrix.scale(-1, 1); + break; + default: + break; + } + + shader->setUniform(GLShader::ModelViewProjectionMatrix, mvpMatrix); + + initRenderTarget(output); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + GLRenderTarget::setKWinFramebuffer(0); + glBindTexture(GL_TEXTURE_2D, output.render.texture); + output.render.vbo->render(GL_TRIANGLES); + ShaderManager::instance()->popShader(); + glBindTexture(GL_TEXTURE_2D, 0); + } else { + // secondary GPU: render on primary and import framebuffer + renderingBackend()->renderFramebufferToSurface(output.output); + uint32_t stride = 0; + uint32_t format = 0; + int fd = renderingBackend()->getDmabufForSecondaryGpuOutput(output.output, &format, &stride); + if (fd != -1) { + struct gbm_import_fd_data data = {}; + data.fd = fd; + data.width = (uint32_t) size.width(); + data.height = (uint32_t) size.height(); + data.stride = stride; + data.format = format; + gbm_bo *importedBuffer = gbm_bo_import(m_gpu->gbmDevice(), GBM_BO_IMPORT_FD, &data, GBM_BO_USE_SCANOUT | GBM_BO_USE_LINEAR); + if (!importedBuffer) { + qCDebug(KWIN_DRM) << "failed to import dma-buf!" << strerror(errno); + } else { + // this buffer automatically gets destroyed by the DrmSurfaceBuffer class + output.importedGbmBo = importedBuffer; + } + } + renderingBackend()->cleanupDmabufForSecondaryGpuOutput(output.output); } - switch (drmOutput->transform()) { - case DrmOutput::Transform::Flipped: - case DrmOutput::Transform::Flipped90: - case DrmOutput::Transform::Flipped180: - case DrmOutput::Transform::Flipped270: - mvpMatrix.scale(-1, 1); - break; - default: - break; - } - - shader->setUniform(GLShader::ModelViewProjectionMatrix, mvpMatrix); - - glBindTexture(GL_TEXTURE_2D, output.render.texture); - output.render.vbo->render(GL_TRIANGLES); - ShaderManager::instance()->popShader(); } void EglGbmBackend::prepareRenderFramebuffer(const Output &output) const @@ -370,6 +534,7 @@ void EglGbmBackend::prepareRenderFramebuffer(const Output &output) const bool EglGbmBackend::makeContextCurrent(const Output &output) const { + Q_ASSERT(isPrimary()); const EGLSurface surface = output.eglSurface; if (surface == EGL_NO_SURFACE) { return false; @@ -483,14 +648,21 @@ void EglGbmBackend::aboutToStartPainting(int screenId, const QRegion &damagedReg void EglGbmBackend::presentOnOutput(Output &output, const QRegion &damagedRegion) { - if (supportsSwapBuffersWithDamage()) { - QVector rects = regionToRects(output.damageHistory.constFirst(), output.output); - eglSwapBuffersWithDamageEXT(eglDisplay(), output.eglSurface, - rects.data(), rects.count()/4); + if (isPrimary()) { + if (supportsSwapBuffersWithDamage()) { + QVector rects = regionToRects(output.damageHistory.constFirst(), output.output); + eglSwapBuffersWithDamageEXT(eglDisplay(), output.eglSurface, + rects.data(), rects.count()/4); + } else { + eglSwapBuffers(eglDisplay(), output.eglSurface); + } + output.buffer = new DrmSurfaceBuffer(m_gpu->fd(), output.gbmSurface); + } else if (output.importedGbmBo == nullptr) { + qCDebug(KWIN_DRM) << "imported gbm_bo does not exist!"; + return; } else { - eglSwapBuffers(eglDisplay(), output.eglSurface); + output.buffer = new DrmSurfaceBuffer(m_gpu->fd(), output.importedGbmBo); } - output.buffer = new DrmSurfaceBuffer(m_gpu->fd(), output.gbmSurface); Q_EMIT output.output->outputChange(damagedRegion); m_backend->present(output.buffer, output.output); @@ -500,12 +672,6 @@ void EglGbmBackend::presentOnOutput(Output &output, const QRegion &damagedRegion } } -void EglGbmBackend::screenGeometryChanged(const QSize &size) -{ - Q_UNUSED(size) - // TODO, create new buffer? -} - SceneOpenGLTexturePrivate *EglGbmBackend::createBackendTexture(SceneOpenGLTexture *texture) { return new EglGbmTexture(texture, this); @@ -523,8 +689,15 @@ void EglGbmBackend::setViewport(const Output &output) const QRegion EglGbmBackend::beginFrame(int screenId) { - const Output &output = m_outputs.at(screenId); + if (isPrimary()) { + return prepareRenderingForOutput(m_outputs.at(screenId)); + } else { + return renderingBackend()->beginFrameForSecondaryGpu(m_outputs.at(screenId).output); + } +} +QRegion EglGbmBackend::prepareRenderingForOutput(const Output &output) const +{ makeContextCurrent(output); prepareRenderFramebuffer(output); setViewport(output); @@ -577,16 +750,6 @@ void EglGbmBackend::endFrame(int screenId, const QRegion &renderedRegion, } } -bool EglGbmBackend::usesOverlayWindow() const -{ - return false; -} - -bool EglGbmBackend::perScreenRendering() const -{ - return true; -} - QSharedPointer EglGbmBackend::textureForOutput(AbstractOutput *abstractOutput) const { const QVector::const_iterator itOutput = std::find_if(m_outputs.begin(), m_outputs.end(), diff --git a/plugins/platforms/drm/egl_gbm_backend.h b/plugins/platforms/drm/egl_gbm_backend.h index 3f69b61e05..72878c0f03 100644 --- a/plugins/platforms/drm/egl_gbm_backend.h +++ b/plugins/platforms/drm/egl_gbm_backend.h @@ -8,41 +8,48 @@ */ #ifndef KWIN_EGL_GBM_BACKEND_H #define KWIN_EGL_GBM_BACKEND_H -#include "abstract_egl_backend.h" +#include "abstract_egl_drm_backend.h" #include struct gbm_surface; +struct gbm_bo; namespace KWin { class AbstractOutput; -class DrmBackend; class DrmBuffer; class DrmSurfaceBuffer; class DrmOutput; class GbmSurface; -class DrmGpu; /** * @brief OpenGL Backend using Egl on a GBM surface. */ -class EglGbmBackend : public AbstractEglBackend +class EglGbmBackend : public AbstractEglDrmBackend { Q_OBJECT public: EglGbmBackend(DrmBackend *drmBackend, DrmGpu *gpu); - ~EglGbmBackend() override; - void screenGeometryChanged(const QSize &size) override; + SceneOpenGLTexturePrivate *createBackendTexture(SceneOpenGLTexture *texture) override; - bool usesOverlayWindow() const override; - bool perScreenRendering() const override; QRegion beginFrame(int screenId) override; void endFrame(int screenId, const QRegion &damage, const QRegion &damagedRegion) override; void init() override; QSharedPointer textureForOutput(AbstractOutput *requestedOutput) const override; + int screenCount() const override { + return m_outputs.count(); + } + + void addSecondaryGpuOutput(AbstractOutput *output) override; + int getDmabufForSecondaryGpuOutput(AbstractOutput *output, uint32_t *format, uint32_t *stride) override; + void cleanupDmabufForSecondaryGpuOutput(AbstractOutput *output) override; + void removeSecondaryGpuOutput(AbstractOutput *output) override; + QRegion beginFrameForSecondaryGpu(AbstractOutput *output) override; + void renderFramebufferToSurface(AbstractOutput *output) override; + protected: void present() override; void cleanupSurfaces() override; @@ -69,11 +76,16 @@ private: GLuint texture = 0; std::shared_ptr vbo; } render; + + bool onSecondaryGPU = false; + int dmabufFd = 0; + gbm_bo *secondaryGbmBo = nullptr; + gbm_bo *importedGbmBo = nullptr; }; void createOutput(DrmOutput *drmOutput); bool resetOutput(Output &output, DrmOutput *drmOutput); - std::shared_ptr createGbmSurface(const QSize &size) const; + std::shared_ptr createGbmSurface(const QSize &size, const bool linear) const; EGLSurface createEglSurface(std::shared_ptr gbmSurface) const; bool makeContextCurrent(const Output &output) const; @@ -84,6 +96,7 @@ private: void prepareRenderFramebuffer(const Output &output) const; void renderFramebufferToSurface(Output &output); + QRegion prepareRenderingForOutput(const Output &output) const; void presentOnOutput(Output &output, const QRegion &damagedRegion); @@ -91,9 +104,9 @@ private: void cleanupOutput(Output &output); void cleanupFramebuffer(Output &output); - DrmBackend *m_backend; - DrmGpu *m_gpu; QVector m_outputs; + QVector m_secondaryGpuOutputs; + friend class EglGbmTexture; }; diff --git a/plugins/platforms/drm/egl_multi_backend.cpp b/plugins/platforms/drm/egl_multi_backend.cpp new file mode 100644 index 0000000000..afcaee22c8 --- /dev/null +++ b/plugins/platforms/drm/egl_multi_backend.cpp @@ -0,0 +1,148 @@ +/* + 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 "egl_multi_backend.h" +#include "logging.h" + +namespace KWin +{ + +EglMultiBackend::EglMultiBackend(AbstractEglDrmBackend *backend0) : OpenGLBackend() +{ + m_backends.append(backend0); + setIsDirectRendering(true); + setSyncsToVBlank(true); +} + +EglMultiBackend::~EglMultiBackend() +{ + for (int i = 1; i < m_backends.count(); i++) { + delete m_backends[i]; + } + // delete primary backend last, or this will crash! + delete m_backends[0]; +} + +void EglMultiBackend::init() +{ + for (auto b : qAsConst(m_backends)) { + b->init(); + } + // set all the values: + // if any block, set it to be blocking + setBlocksForRetrace(false); + // if any don't support it set it to not supported + setSupportsBufferAge(true); + setSupportsPartialUpdate(true); + setSupportsSwapBuffersWithDamage(true); + for (auto b : qAsConst(m_backends)) { + if (b->blocksForRetrace()) { + setBlocksForRetrace(true); + } + if (!b->supportsBufferAge()) { + setSupportsBufferAge(false); + } + if (!b->supportsPartialUpdate()) { + setSupportsPartialUpdate(false); + } + if (!b->supportsSwapBuffersWithDamage()) { + setSupportsSwapBuffersWithDamage(false); + } + } + // we only care about the rendering GPU here + setSupportsSurfacelessContext(m_backends[0]->supportsSurfacelessContext()); + // these are client extensions and the same for all egl backends + setExtensions(m_backends[0]->extensions()); + + m_backends[0]->makeCurrent(); +} + +QRegion EglMultiBackend::beginFrame(int screenId) +{ + int internalScreenId; + AbstractEglBackend *backend = findBackend(screenId, internalScreenId); + Q_ASSERT(backend != nullptr); + return backend->beginFrame(internalScreenId); +} + +void EglMultiBackend::endFrame(int screenId, const QRegion &damage, const QRegion &damagedRegion) +{ + int internalScreenId; + AbstractEglBackend *backend = findBackend(screenId, internalScreenId); + Q_ASSERT(backend != nullptr); + backend->endFrame(internalScreenId, damage, damagedRegion); +} + +bool EglMultiBackend::makeCurrent() +{ + return m_backends[0]->makeCurrent(); +} + +void EglMultiBackend::doneCurrent() +{ + m_backends[0]->doneCurrent(); +} + +SceneOpenGLTexturePrivate *EglMultiBackend::createBackendTexture(SceneOpenGLTexture *texture) +{ + return m_backends[0]->createBackendTexture(texture); +} + +QSharedPointer EglMultiBackend::textureForOutput(AbstractOutput *requestedOutput) const +{ + // this assumes that the wrong backends return {} + for (auto backend : qAsConst(m_backends)) { + auto texture = backend->textureForOutput(requestedOutput); + if (!texture.isNull()) { + return texture; + } + } + return {}; +} + +bool EglMultiBackend::usesOverlayWindow() const +{ + return false; +} + +bool EglMultiBackend::perScreenRendering() const +{ + return true; +} + +void EglMultiBackend::screenGeometryChanged(const QSize &size) +{ + Q_UNUSED(size) +} + +void EglMultiBackend::present() +{ + Q_UNREACHABLE(); +} + +AbstractEglDrmBackend *EglMultiBackend::findBackend(int screenId, int& internalScreenId) +{ + int screens = 0; + for (int i = 0; i < m_backends.count(); i++) { + if (screenId < screens + m_backends[i]->screenCount()) { + internalScreenId = screenId - screens; + return m_backends[i]; + } + screens += m_backends[i]->screenCount(); + } + qCDebug(KWIN_DRM) << "could not find backend!" << screenId << "/" << screens; + return nullptr; +} + +void EglMultiBackend::addBackend(AbstractEglDrmBackend *backend) +{ + m_backends.append(backend); +} + +} diff --git a/plugins/platforms/drm/egl_multi_backend.h b/plugins/platforms/drm/egl_multi_backend.h new file mode 100644 index 0000000000..737250dc8e --- /dev/null +++ b/plugins/platforms/drm/egl_multi_backend.h @@ -0,0 +1,54 @@ +/* + 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 EGLMULTIBACKEND_H +#define EGLMULTIBACKEND_H + +#include "abstract_egl_drm_backend.h" + +namespace KWin +{ + +class EglMultiBackend : public OpenGLBackend +{ +public: + EglMultiBackend(AbstractEglDrmBackend *backend0); + ~EglMultiBackend(); + + void init() override; + + QRegion beginFrame(int screenId) override; + void endFrame(int screenId, const QRegion &damage, const QRegion &damagedRegion) override; + + bool makeCurrent() override; + void doneCurrent() override; + + SceneOpenGLTexturePrivate *createBackendTexture(SceneOpenGLTexture *texture) override; + QSharedPointer textureForOutput(AbstractOutput *requestedOutput) const override; + + bool usesOverlayWindow() const override; + bool perScreenRendering() const override; + + void screenGeometryChanged(const QSize &size) override; + + void addBackend(AbstractEglDrmBackend *backend); + +protected: + void present() override; + +private: + QVector m_backends; + + AbstractEglDrmBackend *findBackend(int screenId, int& internalScreenId); + +}; + +} + +#endif // EGLMULTIBACKEND_H diff --git a/plugins/platforms/drm/egl_stream_backend.cpp b/plugins/platforms/drm/egl_stream_backend.cpp index 4590284ee2..706e16889b 100644 --- a/plugins/platforms/drm/egl_stream_backend.cpp +++ b/plugins/platforms/drm/egl_stream_backend.cpp @@ -69,11 +69,9 @@ PFNEGLQUERYWAYLANDBUFFERWL pEglQueryWaylandBufferWL = nullptr; #define EGL_WAYLAND_Y_INVERTED_WL 0x31DB #endif -EglStreamBackend::EglStreamBackend(DrmBackend *b, DrmGpu *gpu) - : AbstractEglBackend(), m_backend(b), m_gpu(gpu) +EglStreamBackend::EglStreamBackend(DrmBackend *drmBackend, DrmGpu *gpu) + : AbstractEglDrmBackend(drmBackend, gpu) { - setIsDirectRendering(true); - setSyncsToVBlank(true); connect(m_gpu, &DrmGpu::outputEnabled, this, &EglStreamBackend::createOutput); connect(m_gpu, &DrmGpu::outputDisabled, this, [this] (DrmOutput *output) { @@ -89,11 +87,6 @@ EglStreamBackend::EglStreamBackend(DrmBackend *b, DrmGpu *gpu) }); } -EglStreamBackend::~EglStreamBackend() -{ - cleanup(); -} - void EglStreamBackend::cleanupSurfaces() { for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { @@ -285,7 +278,7 @@ bool EglStreamBackend::initRenderingContext() return false; } - const auto outputs = m_backend->drmOutputs(); + const auto outputs = m_gpu->outputs(); for (DrmOutput *drmOutput : outputs) { createOutput(drmOutput); } @@ -465,11 +458,6 @@ void EglStreamBackend::presentOnOutput(EglStreamBackend::Output &o) } } -void EglStreamBackend::screenGeometryChanged(const QSize &size) -{ - Q_UNUSED(size) -} - SceneOpenGLTexturePrivate *EglStreamBackend::createBackendTexture(SceneOpenGLTexture *texture) { return new EglStreamTexture(texture, this); @@ -490,16 +478,6 @@ void EglStreamBackend::endFrame(int screenId, const QRegion &renderedRegion, con presentOnOutput(o); } -bool EglStreamBackend::usesOverlayWindow() const -{ - return false; -} - -bool EglStreamBackend::perScreenRendering() const -{ - return true; -} - /************************************************ * EglTexture ************************************************/ diff --git a/plugins/platforms/drm/egl_stream_backend.h b/plugins/platforms/drm/egl_stream_backend.h index 014c2b9eee..c88b987e29 100644 --- a/plugins/platforms/drm/egl_stream_backend.h +++ b/plugins/platforms/drm/egl_stream_backend.h @@ -8,7 +8,7 @@ */ #ifndef KWIN_EGL_STREAM_BACKEND_H #define KWIN_EGL_STREAM_BACKEND_H -#include "abstract_egl_backend.h" +#include "abstract_egl_drm_backend.h" #include #include #include @@ -16,28 +16,26 @@ namespace KWin { -class DrmBackend; class DrmOutput; class DrmBuffer; -class DrmGpu; /** * @brief OpenGL Backend using Egl with an EGLDevice. */ -class EglStreamBackend : public AbstractEglBackend +class EglStreamBackend : public AbstractEglDrmBackend { Q_OBJECT public: EglStreamBackend(DrmBackend *b, DrmGpu *gpu); - ~EglStreamBackend() override; - void screenGeometryChanged(const QSize &size) override; SceneOpenGLTexturePrivate *createBackendTexture(SceneOpenGLTexture *texture) override; - bool usesOverlayWindow() const override; - bool perScreenRendering() const override; QRegion beginFrame(int screenId) override; void endFrame(int screenId, const QRegion &damage, const QRegion &damagedRegion) override; void init() override; + int screenCount() const override { + return m_outputs.count(); + } + protected: void present() override; void cleanupSurfaces() override; @@ -68,8 +66,6 @@ private: void cleanupOutput(const Output &output); void createOutput(DrmOutput *output); - DrmBackend *m_backend; - DrmGpu *m_gpu; QVector m_outputs; KWaylandServer::EglStreamControllerInterface *m_eglStreamControllerInterface; QHash m_streamTextures;