Commit 9ab68806 authored by Xaver Hugl's avatar Xaver Hugl
Browse files

Create egl backend for multiple backends and enable multi-gpu usage for the gbm backend

parent 2a8395c7
......@@ -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)
......
......@@ -51,6 +51,17 @@ public:
QSharedPointer<GLTexture> 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<QByteArray> m_clientExtensions;
// note: m_dmaBuf is nullptr if this is not the primary backend
EglDmabuf *m_dmaBuf = nullptr;
QList<QByteArray> m_clientExtensions;
static AbstractEglBackend * s_primaryBackend;
};
class KWIN_EXPORT AbstractEglTexture : public SceneOpenGLTexturePrivate
......
......@@ -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)
......
/*
* KWin - the KDE window manager
* This file is part of the KDE project.
*
* SPDX-FileCopyrightText: 2020 Xaver Hugl <xaver.hugl@gmail.com>
*
* 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;
}
/*
* KWin - the KDE window manager
* This file is part of the KDE project.
*
* SPDX-FileCopyrightText: 2020 Xaver Hugl <xaver.hugl@gmail.com>
*
* 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<AbstractEglDrmBackend*>(primaryBackend());
}
protected:
AbstractEglDrmBackend(DrmBackend *drmBackend, DrmGpu *gpu);
DrmBackend *m_backend;
DrmGpu *m_gpu;
};
}
#endif // KWIN_ABSTRACTEGLDRMBACKEND_H
......@@ -47,6 +47,7 @@
#include <libdrm/drm_mode.h>
#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;
......
......@@ -41,6 +41,17 @@ DrmSurfaceBuffer::DrmSurfaceBuffer(int fd, const std::shared_ptr<GbmSurface> &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;
}
......
......@@ -25,6 +25,7 @@ class DrmSurfaceBuffer : public DrmBuffer
{
public:
DrmSurfaceBuffer(int fd, const std::shared_ptr<GbmSurface> &surface);
DrmSurfaceBuffer(int fd, gbm_bo *buffer);
~DrmSurfaceBuffer() override;
bool needsModeChange(DrmBuffer *b) const override {
......
......@@ -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);
......
......@@ -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;
......
......@@ -21,27 +21,18 @@
#include <kwineglimagetexture.h>
// system
#include <gbm.h>
#include <unistd.h>
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<GbmSurface> EglGbmBackend::createGbmSurface(const QSize &size) const
std::shared_ptr<GbmSurface> 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<GbmSurface>(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,38 +214,163 @@ bool EglGbmBackend::resetOutput(Output &output, DrmOutput *drmOutput)
void EglGbmBackend::createOutput(DrmOutput *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;
}
resetOutput(*it, drmOutput);
}
);
m_outputs << newOutput;
}
} else {
Output newOutput;
newOutput.output = drmOutput;
renderingBackend()->addSecondaryGpuOutput(drmOutput);
m_outputs << newOutput;
}
}
void EglGbmBackend::removeOutput(DrmOutput *drmOutput)
{
auto it = std::find_if(m_outputs.begin(), m_outputs.end(),
[drmOutput] (const Output &output) {
return output.output == drmOutput;
}
);
if (it == m_outputs.end()) {
return;
}
if (this != primaryBackend()) {
renderingBackend()->removeSecondaryGpuOutput((*it).output);
} else {
cleanupOutput(*it);
}
m_outputs.erase(it);
}
void EglGbmBackend::addSecondaryGpuOutput(AbstractOutput *output)
{
DrmOutput *drmOutput = static_cast<DrmOutput*>(output);
Output newOutput;
newOutput.onSecondaryGPU = true;
if (resetOutput(newOutput, drmOutput)) {
connect(drmOutput, &DrmOutput::modeChanged, this,
[drmOutput, this] {
auto it = std::find_if(m_outputs.begin(), m_outputs.end(),
auto it = std::find_if(m_secondaryGpuOutputs.begin(), m_secondaryGpuOutputs.end(),
[drmOutput] (const auto &output) {
return output.output == drmOutput;
}
);
if (it == m_outputs.end()) {
if (it == m_secondaryGpuOutputs.end()) {
return;
}
resetOutput(*it, drmOutput);
}
);
m_outputs << newOutput;
m_secondaryGpuOutputs << newOutput;
}
}
void EglGbmBackend::removeOutput(DrmOutput *drmOutput)
int EglGbmBackend::getDmabufForSecondaryGpuOutput(AbstractOutput *output, uint32_t *format, uint32_t *stride)
{
auto it = std::find_if(m_outputs.begin(), m_outputs.end(),
DrmOutput *drmOutput = static_cast<DrmOutput*>(output);
auto it = std::find_if(m_secondaryGpuOutputs.begin(), m_secondaryGpuOutputs.end(),
[drmOutput] (const Output &output) {
return output.output == drmOutput;
}
);
if (it == m_outputs.end()) {
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;
}