platforms/drm: support NVidia as secondary GPU with CPU copy

BUG: 431062
This commit is contained in:
Xaver Hugl 2021-05-23 19:45:05 +02:00
parent 605f00d03a
commit cfd7af0179
10 changed files with 203 additions and 141 deletions

View file

@ -25,7 +25,7 @@ public:
void screenGeometryChanged(const QSize &size) override;
virtual int screenCount() const = 0;
virtual void addOutput(DrmOutput *output) = 0;
virtual bool addOutput(DrmOutput *output) = 0;
virtual void removeOutput(DrmOutput *output) = 0;
virtual bool swapBuffers(DrmOutput *output) {
Q_UNUSED(output)

View file

@ -186,8 +186,22 @@ bool DrmBackend::initialize()
}
} else {
const auto devices = m_udev->listGPUs();
bool bootVga = false;
for (const UdevDevice::Ptr &device : devices) {
addGpu(device->devNode());
if (addGpu(device->devNode())) {
bootVga |= device->isBootVga();
}
}
// if a boot device is set, honor that setting
// if not, prefer gbm for rendering because that works better
if (!bootVga && !m_gpus.isEmpty() && m_gpus[0]->useEglStreams()) {
for (int i = 1; i < m_gpus.count(); i++) {
if (!m_gpus[i]->useEglStreams()) {
m_gpus.swapItemsAt(i, 0);
break;
}
}
}
}
@ -220,11 +234,10 @@ void DrmBackend::handleUdevEvent()
}
if (device->action() == QStringLiteral("add")) {
if (m_gpus.isEmpty() || !primaryGpu()->useEglStreams()) {
if (addGpu(device->devNode())) {
updateOutputs();
updateCursor();
}
qCDebug(KWIN_DRM) << "New gpu found:" << device->devNode();
if (addGpu(device->devNode())) {
updateOutputs();
updateCursor();
}
} else if (device->action() == QStringLiteral("remove")) {
DrmGpu *gpu = findGpu(device->devNum());
@ -234,6 +247,7 @@ void DrmBackend::handleUdevEvent()
kwinApp()->quit();
return;
} else {
qCDebug(KWIN_DRM) << "Removing gpu" << gpu->devNode();
emit gpuRemoved(gpu);
m_gpus.removeOne(gpu);
delete gpu;
@ -247,7 +261,7 @@ void DrmBackend::handleUdevEvent()
gpu = addGpu(device->devNode());
}
if (gpu) {
qCDebug(KWIN_DRM) << "Received hot plug event for monitored drm device";
qCDebug(KWIN_DRM) << "Received hot plug event for monitored drm device" << gpu->devNode();
updateOutputs();
updateCursor();
}
@ -257,6 +271,9 @@ void DrmBackend::handleUdevEvent()
DrmGpu *DrmBackend::addGpu(const QString &fileName)
{
if (primaryGpu() && primaryGpu()->useEglStreams()) {
return nullptr;
}
int fd = session()->openRestricted(fileName);
if (fd < 0) {
qCWarning(KWIN_DRM) << "failed to open drm device at" << fileName;
@ -280,17 +297,12 @@ DrmGpu *DrmBackend::addGpu(const QString &fileName)
}
DrmGpu *gpu = new DrmGpu(this, fileName, fd, buf.st_rdev);
if (!gpu->useEglStreams() || m_gpus.isEmpty()) {
m_gpus.append(gpu);
m_active = true;
connect(gpu, &DrmGpu::outputAdded, this, &DrmBackend::addOutput);
connect(gpu, &DrmGpu::outputRemoved, this, &DrmBackend::removeOutput);
emit gpuAdded(gpu);
return gpu;
} else {
delete gpu;
return nullptr;
}
m_gpus.append(gpu);
m_active = true;
connect(gpu, &DrmGpu::outputAdded, this, &DrmBackend::addOutput);
connect(gpu, &DrmGpu::outputRemoved, this, &DrmBackend::removeOutput);
emit gpuAdded(gpu);
return gpu;
}
void DrmBackend::addOutput(DrmOutput *o)
@ -319,6 +331,7 @@ void DrmBackend::updateOutputs()
auto gpu = *it;
gpu->updateOutputs();
if (gpu->outputs().isEmpty() && gpu != primaryGpu()) {
qCDebug(KWIN_DRM) << "removing unused GPU" << gpu->devNode();
it = m_gpus.erase(it);
emit gpuRemoved(gpu);
delete gpu;
@ -594,8 +607,7 @@ OpenGLBackend *DrmBackend::createOpenGLBackend()
AbstractEglBackend::setPrimaryBackend(primaryBackend);
EglMultiBackend *backend = new EglMultiBackend(this, primaryBackend);
for (int i = 1; i < m_gpus.count(); i++) {
auto backendi = new EglGbmBackend(this, m_gpus.at(i));
backend->addBackend(backendi);
backend->addGpu(m_gpus[i]);
}
return backend;
#else

View file

@ -656,14 +656,12 @@ bool DrmOutput::presentAtomically(const QSharedPointer<DrmBuffer> &buffer)
return false;
}
#if HAVE_EGL_STREAMS
if (m_gpu->useEglStreams() && !m_modesetRequested) {
// EglStreamBackend queues normal page flips through EGL,
// modesets are still performed through DRM-KMS
// EglStreamBackend queues normal page flips through EGL when used as the rendering backend,
// modesets are still performed through DRM-KMS
if (m_gpu->useEglStreams() && !m_modesetRequested && m_gpu == m_backend->primaryGpu()) {
m_pageFlipPending = true;
return true;
}
#endif
m_primaryPlane->setNext(buffer);
m_nextPlanesFlipList << m_primaryPlane;
@ -814,12 +812,12 @@ bool DrmOutput::doAtomicCommit(AtomicCommitMode mode)
flags |= DRM_MODE_ATOMIC_NONBLOCK;
}
#if HAVE_EGL_STREAMS
if (!m_gpu->useEglStreams())
// EglStreamBackend uses the NV_output_drm_flip_event EGL extension
// to register the flip event through eglStreamConsumerAcquireAttribNV
#endif
// EglStreamBackend uses the NV_output_drm_flip_event EGL extension
// to register the flip event through eglStreamConsumerAcquireAttribNV
// but only when used as the rendering GPU
if (!m_gpu->useEglStreams() || m_gpu != m_backend->primaryGpu()) {
flags |= DRM_MODE_PAGE_FLIP_EVENT;
}
}
} else {
flags |= DRM_MODE_ATOMIC_TEST_ONLY;

View file

@ -32,7 +32,7 @@ DumbSwapchain::DumbSwapchain(DrmGpu *gpu, const QSize &size)
m_buffers << buffer;
}
if (m_buffers.count() < 3) {
qCWarning(KWIN_DRM) << "Failed to create gbm buffers for swapchain!";
qCWarning(KWIN_DRM) << "Failed to create dumb buffers for swapchain!";
m_buffers.clear();
}
}

View file

@ -208,7 +208,7 @@ bool EglGbmBackend::resetOutput(Output &output, DrmOutput *drmOutput)
return true;
}
void EglGbmBackend::addOutput(DrmOutput *drmOutput)
bool EglGbmBackend::addOutput(DrmOutput *drmOutput)
{
if (isPrimary()) {
Output newOutput;
@ -228,13 +228,18 @@ void EglGbmBackend::addOutput(DrmOutput *drmOutput)
}
);
outputs << newOutput;
} else {
return false;
}
} else {
Output newOutput;
newOutput.output = drmOutput;
renderingBackend()->addOutput(drmOutput);
if (!renderingBackend()->addOutput(drmOutput)) {
return false;
}
m_outputs << newOutput;
}
return true;
}
void EglGbmBackend::removeOutput(DrmOutput *drmOutput)
@ -269,7 +274,7 @@ bool EglGbmBackend::swapBuffers(DrmOutput *drmOutput)
renderFramebufferToSurface(*it);
auto error = eglSwapBuffers(eglDisplay(), it->eglSurface);
if (error != EGL_TRUE) {
qCDebug(KWIN_DRM) << "an error occurred while swapping buffers" << error;
qCCritical(KWIN_DRM) << "an error occurred while swapping buffers" << error;
it->secondaryBuffer = nullptr;
return false;
}

View file

@ -57,7 +57,7 @@ public:
return m_outputs.count();
}
void addOutput(DrmOutput *output) override;
bool addOutput(DrmOutput *output) override;
void removeOutput(DrmOutput *output) override;
bool swapBuffers(DrmOutput *output) override;
bool exportFramebuffer(DrmOutput *output, void *data, const QSize &size, uint32_t stride) override;

View file

@ -10,6 +10,7 @@
#include "egl_multi_backend.h"
#include "logging.h"
#include "egl_gbm_backend.h"
#include "egl_stream_backend.h"
#include "drm_backend.h"
#include "drm_gpu.h"
@ -49,6 +50,7 @@ void EglMultiBackend::init()
setExtensions(m_backends[0]->extensions());
m_backends[0]->makeCurrent();
m_initialized = true;
}
QRegion EglMultiBackend::beginFrame(int screenId)
@ -120,11 +122,6 @@ AbstractEglDrmBackend *EglMultiBackend::findBackend(int screenId, int& internalS
return nullptr;
}
void EglMultiBackend::addBackend(AbstractEglDrmBackend *backend)
{
m_backends.append(backend);
}
bool EglMultiBackend::directScanoutAllowed(int screenId) const
{
int internalScreenId;
@ -135,9 +132,15 @@ bool EglMultiBackend::directScanoutAllowed(int screenId) const
void EglMultiBackend::addGpu(DrmGpu *gpu)
{
// secondary GPUs are atm guaranteed to be gbm
auto backend = new EglGbmBackend(m_platform, gpu);
backend->init();
AbstractEglDrmBackend *backend;
if (gpu->useEglStreams()) {
backend = new EglStreamBackend(m_platform, gpu);
} else {
backend = new EglGbmBackend(m_platform, gpu);
}
if (m_initialized) {
backend->init();
}
m_backends.append(backend);
}

View file

@ -37,8 +37,6 @@ public:
void screenGeometryChanged(const QSize &size) override;
void addBackend(AbstractEglDrmBackend *backend);
bool directScanoutAllowed(int screen) const override;
public Q_SLOTS:
@ -48,6 +46,7 @@ public Q_SLOTS:
private:
DrmBackend *m_platform;
QVector<AbstractEglDrmBackend*> m_backends;
bool m_initialized = false;
AbstractEglDrmBackend *findBackend(int screenId, int& internalScreenId) const;
};

View file

@ -22,12 +22,14 @@
#include "wayland_server.h"
#include <kwinglplatform.h>
#include <kwingltexture.h>
#include "drm_gpu.h"
#include "dumb_swapchain.h"
#include <QOpenGLContext>
#include <KWaylandServer/buffer_interface.h>
#include <KWaylandServer/display.h>
#include <KWaylandServer/eglstream_controller_interface.h>
#include <KWaylandServer/resource.h>
#include "drm_gpu.h"
namespace KWin
{
@ -250,23 +252,31 @@ void EglStreamBackend::init()
return;
}
if (!initializeEgl()) {
setFailed("Failed to initialize EGL api");
return;
}
if (!initRenderingContext()) {
setFailed("Failed to initialize rendering context");
return;
}
if (isPrimary()) {
if (!initializeEgl()) {
setFailed("Failed to initialize EGL api");
return;
}
if (!initRenderingContext()) {
setFailed("Failed to initialize rendering context");
return;
}
initKWinGL();
setSupportsBufferAge(false);
initWayland();
initKWinGL();
setSupportsBufferAge(false);
initWayland();
using namespace KWaylandServer;
m_eglStreamControllerInterface = new EglStreamControllerInterface(waylandServer()->display());
connect(m_eglStreamControllerInterface, &EglStreamControllerInterface::streamConsumerAttached, this,
&EglStreamBackend::attachStreamConsumer);
using namespace KWaylandServer;
m_eglStreamControllerInterface = new EglStreamControllerInterface(waylandServer()->display());
connect(m_eglStreamControllerInterface, &EglStreamControllerInterface::streamConsumerAttached, this,
&EglStreamBackend::attachStreamConsumer);
} else {
// secondary NVidia GPUs only import dumb buffers
const auto outputs = m_gpu->outputs();
for (DrmOutput *drmOutput : outputs) {
addOutput(drmOutput);
}
}
}
bool EglStreamBackend::initRenderingContext()
@ -297,69 +307,80 @@ bool EglStreamBackend::resetOutput(Output &o, DrmOutput *drmOutput)
// dumb buffer used for modesetting
o.buffer = QSharedPointer<DrmDumbBuffer>::create(m_gpu, drmOutput->pixelSize());
EGLAttrib streamAttribs[] = {
EGL_STREAM_FIFO_LENGTH_KHR, 0, // mailbox mode
EGL_CONSUMER_AUTO_ACQUIRE_EXT, EGL_FALSE,
EGL_NONE
};
EGLStreamKHR stream = pEglCreateStreamAttribNV(eglDisplay(), streamAttribs);
if (stream == EGL_NO_STREAM_KHR) {
qCCritical(KWIN_DRM) << "Failed to create EGL stream for output";
return false;
}
EGLAttrib outputAttribs[3];
if (drmOutput->primaryPlane()) {
outputAttribs[0] = EGL_DRM_PLANE_EXT;
outputAttribs[1] = drmOutput->primaryPlane()->id();
} else {
outputAttribs[0] = EGL_DRM_CRTC_EXT;
outputAttribs[1] = drmOutput->crtc()->id();
}
outputAttribs[2] = EGL_NONE;
EGLint numLayers;
EGLOutputLayerEXT outputLayer;
pEglGetOutputLayersEXT(eglDisplay(), outputAttribs, &outputLayer, 1, &numLayers);
if (numLayers == 0) {
qCCritical(KWIN_DRM) << "No EGL output layers found";
return false;
}
pEglStreamConsumerOutputEXT(eglDisplay(), stream, outputLayer);
EGLint streamProducerAttribs[] = {
EGL_WIDTH, drmOutput->pixelSize().width(),
EGL_HEIGHT, drmOutput->pixelSize().height(),
EGL_NONE
};
EGLSurface eglSurface = pEglCreateStreamProducerSurfaceKHR(eglDisplay(), config(), stream,
streamProducerAttribs);
if (eglSurface == EGL_NO_SURFACE) {
qCCritical(KWIN_DRM) << "Failed to create EGL surface for output";
return false;
}
if (o.eglSurface != EGL_NO_SURFACE) {
if (surface() == o.eglSurface) {
setSurface(eglSurface);
if (isPrimary()) {
EGLAttrib streamAttribs[] = {
EGL_STREAM_FIFO_LENGTH_KHR, 0, // mailbox mode
EGL_CONSUMER_AUTO_ACQUIRE_EXT, EGL_FALSE,
EGL_NONE
};
EGLStreamKHR stream = pEglCreateStreamAttribNV(eglDisplay(), streamAttribs);
if (stream == EGL_NO_STREAM_KHR) {
qCCritical(KWIN_DRM) << "Failed to create EGL stream for output";
return false;
}
eglDestroySurface(eglDisplay(), o.eglSurface);
}
if (o.eglStream != EGL_NO_STREAM_KHR) {
pEglDestroyStreamKHR(eglDisplay(), o.eglStream);
}
EGLAttrib outputAttribs[3];
if (drmOutput->primaryPlane()) {
outputAttribs[0] = EGL_DRM_PLANE_EXT;
outputAttribs[1] = drmOutput->primaryPlane()->id();
} else {
outputAttribs[0] = EGL_DRM_CRTC_EXT;
outputAttribs[1] = drmOutput->crtc()->id();
}
outputAttribs[2] = EGL_NONE;
EGLint numLayers;
EGLOutputLayerEXT outputLayer;
pEglGetOutputLayersEXT(eglDisplay(), outputAttribs, &outputLayer, 1, &numLayers);
if (numLayers == 0) {
qCCritical(KWIN_DRM) << "No EGL output layers found";
return false;
}
o.eglStream = stream;
o.eglSurface = eglSurface;
pEglStreamConsumerOutputEXT(eglDisplay(), stream, outputLayer);
EGLint streamProducerAttribs[] = {
EGL_WIDTH, drmOutput->pixelSize().width(),
EGL_HEIGHT, drmOutput->pixelSize().height(),
EGL_NONE
};
EGLSurface eglSurface = pEglCreateStreamProducerSurfaceKHR(eglDisplay(), config(), stream,
streamProducerAttribs);
if (eglSurface == EGL_NO_SURFACE) {
qCCritical(KWIN_DRM) << "Failed to create EGL surface for output";
return false;
}
if (o.eglSurface != EGL_NO_SURFACE) {
if (surface() == o.eglSurface) {
setSurface(eglSurface);
}
eglDestroySurface(eglDisplay(), o.eglSurface);
}
if (o.eglStream != EGL_NO_STREAM_KHR) {
pEglDestroyStreamKHR(eglDisplay(), o.eglStream);
}
o.eglStream = stream;
o.eglSurface = eglSurface;
} else {
QSize size = drmOutput->hardwareTransforms() ? drmOutput->pixelSize() : drmOutput->modeSize();
o.dumbSwapchain = QSharedPointer<DumbSwapchain>::create(m_gpu, size);
if (o.dumbSwapchain->isEmpty()) {
return false;
}
}
return true;
}
void EglStreamBackend::addOutput(DrmOutput *drmOutput)
bool EglStreamBackend::addOutput(DrmOutput *drmOutput)
{
Q_ASSERT(drmOutput->gpu() == m_gpu);
Output o;
if (!resetOutput(o, drmOutput)) {
return;
return false;
}
if (!isPrimary() && !renderingBackend()->addOutput(drmOutput)) {
return false;
}
connect(drmOutput, &DrmOutput::modeChanged, this,
@ -376,6 +397,7 @@ void EglStreamBackend::addOutput(DrmOutput *drmOutput)
}
);
m_outputs << o;
return true;
}
void EglStreamBackend::removeOutput(DrmOutput *drmOutput)
@ -391,6 +413,9 @@ void EglStreamBackend::removeOutput(DrmOutput *drmOutput)
}
cleanupOutput(*it);
m_outputs.erase(it);
if (!isPrimary()) {
renderingBackend()->removeOutput(drmOutput);
}
}
bool EglStreamBackend::makeContextCurrent(const Output &output)
@ -446,16 +471,6 @@ bool EglStreamBackend::initBufferConfigs()
return true;
}
bool EglStreamBackend::presentOnOutput(EglStreamBackend::Output &o)
{
if (!eglSwapBuffers(eglDisplay(), o.eglSurface)) {
qCCritical(KWIN_DRM, "eglSwapBuffers() failed: %x", eglGetError());
return false;
}
return o.output->present(o.buffer);
}
PlatformSurfaceTexture *EglStreamBackend::createPlatformSurfaceTextureInternal(SurfacePixmapInternal *pixmap)
{
return new BasicEGLSurfaceTextureInternal(this, pixmap);
@ -469,8 +484,12 @@ PlatformSurfaceTexture *EglStreamBackend::createPlatformSurfaceTextureWayland(Su
QRegion EglStreamBackend::beginFrame(int screenId)
{
const Output &o = m_outputs.at(screenId);
makeContextCurrent(o);
return o.output->geometry();
if (isPrimary()) {
makeContextCurrent(o);
return o.output->geometry();
} else {
return renderingBackend()->beginFrameForSecondaryGpu(o.output);
}
}
void EglStreamBackend::endFrame(int screenId, const QRegion &renderedRegion, const QRegion &damagedRegion)
@ -481,18 +500,41 @@ void EglStreamBackend::endFrame(int screenId, const QRegion &renderedRegion, con
Output &renderOutput = m_outputs[screenId];
DrmOutput *drmOutput = renderOutput.output;
if (!presentOnOutput(renderOutput)) {
RenderLoopPrivate *renderLoopPrivate = RenderLoopPrivate::get(drmOutput->renderLoop());
renderLoopPrivate->notifyFrameFailed();
return;
bool frameFailed = false;
QSharedPointer<DrmDumbBuffer> buffer;
if (isPrimary()) {
buffer = renderOutput.buffer;
if (!eglSwapBuffers(eglDisplay(), renderOutput.eglSurface)) {
qCCritical(KWIN_DRM, "eglSwapBuffers() failed: %x", eglGetError());
frameFailed = true;
}
} else {
if (!renderingBackend()->swapBuffers(drmOutput)) {
qCCritical(KWIN_DRM) << "swapping buffers on render backend for" << drmOutput << "failed!";
frameFailed = true;
}
buffer = renderOutput.dumbSwapchain->acquireBuffer();
if (!frameFailed && !renderingBackend()->exportFramebuffer(drmOutput, buffer->data(), buffer->size(), buffer->stride())) {
qCCritical(KWIN_DRM) << "importing framebuffer from render backend for" << drmOutput << "failed!";
frameFailed = true;
}
}
if (!frameFailed && !renderOutput.output->present(buffer)) {
frameFailed = true;
}
EGLAttrib acquireAttribs[] = {
EGL_DRM_FLIP_EVENT_DATA_NV, (EGLAttrib)drmOutput,
EGL_NONE,
};
if (!pEglStreamConsumerAcquireAttribNV(eglDisplay(), renderOutput.eglStream, acquireAttribs)) {
qCWarning(KWIN_DRM) << "Failed to acquire output EGL stream frame";
if (frameFailed) {
RenderLoopPrivate *renderLoopPrivate = RenderLoopPrivate::get(drmOutput->renderLoop());
renderLoopPrivate->notifyFrameFailed();
} else if (isPrimary()) {
EGLAttrib acquireAttribs[] = {
EGL_DRM_FLIP_EVENT_DATA_NV, (EGLAttrib)drmOutput,
EGL_NONE,
};
if (!pEglStreamConsumerAcquireAttribNV(eglDisplay(), renderOutput.eglStream, acquireAttribs)) {
qCWarning(KWIN_DRM) << "Failed to acquire output EGL stream frame";
}
}
}

View file

@ -18,7 +18,8 @@ namespace KWin
{
class DrmOutput;
class DrmBuffer;
class DrmDumbBuffer;
class DumbSwapchain;
/**
* @brief OpenGL Backend using Egl with an EGLDevice.
@ -39,7 +40,7 @@ public:
return m_outputs.count();
}
void addOutput(DrmOutput *output) override;
bool addOutput(DrmOutput *output) override;
void removeOutput(DrmOutput *output) override;
protected:
@ -61,13 +62,15 @@ private:
struct Output
{
DrmOutput *output = nullptr;
QSharedPointer<DrmBuffer> buffer;
QSharedPointer<DrmDumbBuffer> buffer;
EGLSurface eglSurface = EGL_NO_SURFACE;
EGLStreamKHR eglStream = EGL_NO_STREAM_KHR;
// for operation as secondary GPU
QSharedPointer<DumbSwapchain> dumbSwapchain;
};
bool resetOutput(Output &output, DrmOutput *drmOutput);
bool makeContextCurrent(const Output &output);
bool presentOnOutput(Output &output);
void cleanupOutput(const Output &output);
QVector<Output> m_outputs;