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

This commit is contained in:
Xaver Hugl 2020-11-28 17:53:41 +00:00
parent 2a8395c7dd
commit 9ab688067a
16 changed files with 704 additions and 172 deletions

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -0,0 +1,43 @@
/*
* 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;
}

View file

@ -0,0 +1,68 @@
/*
* 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

View file

@ -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;

View file

@ -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;
}

View file

@ -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 {

View file

@ -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);

View file

@ -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;

View file

@ -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,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<DrmOutput*>(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<DrmOutput*>(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<DrmOutput*>(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<DrmOutput*>(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<DrmOutput*>(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<DrmOutput*>(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<EGLint> rects = regionToRects(output.damageHistory.constFirst(), output.output);
eglSwapBuffersWithDamageEXT(eglDisplay(), output.eglSurface,
rects.data(), rects.count()/4);
if (isPrimary()) {
if (supportsSwapBuffersWithDamage()) {
QVector<EGLint> 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<GLTexture> EglGbmBackend::textureForOutput(AbstractOutput *abstractOutput) const
{
const QVector<KWin::EglGbmBackend::Output>::const_iterator itOutput = std::find_if(m_outputs.begin(), m_outputs.end(),

View file

@ -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 <memory>
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<GLTexture> 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<GLVertexBuffer> 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<GbmSurface> createGbmSurface(const QSize &size) const;
std::shared_ptr<GbmSurface> createGbmSurface(const QSize &size, const bool linear) const;
EGLSurface createEglSurface(std::shared_ptr<GbmSurface> 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<Output> m_outputs;
QVector<Output> m_secondaryGpuOutputs;
friend class EglGbmTexture;
};

View file

@ -0,0 +1,148 @@
/*
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 "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<GLTexture> 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);
}
}

View file

@ -0,0 +1,54 @@
/*
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 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<GLTexture> 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<AbstractEglDrmBackend*> m_backends;
AbstractEglDrmBackend *findBackend(int screenId, int& internalScreenId);
};
}
#endif // EGLMULTIBACKEND_H

View file

@ -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
************************************************/

View file

@ -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 <KWaylandServer/surface_interface.h>
#include <KWaylandServer/eglstream_controller_interface.h>
#include <wayland-server-core.h>
@ -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<Output> m_outputs;
KWaylandServer::EglStreamControllerInterface *m_eglStreamControllerInterface;
QHash<KWaylandServer::SurfaceInterface *, StreamTexture> m_streamTextures;