platforms/drm: kill the EglStreams backend

The proprietary NVidia driver now supports gbm, which vastly improves the
user experience. For older devices that will not get gbm support dropping
EglStreams will likely not have a big impact as it has several session breaking
issues anyways.

By removing the backend a lot of logic can be simplified, most notably multi-gpu.
This commit is contained in:
Xaver Hugl 2021-11-10 12:17:40 +01:00
parent f91ae3e975
commit bad5752110
11 changed files with 6 additions and 1009 deletions

View file

@ -230,11 +230,6 @@ else()
set(HAVE_GBM_BO_GET_FD_FOR_PLANE 0)
endif()
option(KWIN_BUILD_EGL_STREAM_BACKEND "Enable building of EGLStream based DRM backend" ON)
if (KWIN_BUILD_EGL_STREAM_BACKEND)
set(HAVE_EGL_STREAMS TRUE)
endif()
find_package(X11)
set_package_properties(X11 PROPERTIES
DESCRIPTION "X11 libraries"

View file

@ -30,12 +30,6 @@ if (HAVE_GBM)
)
endif()
if (HAVE_EGL_STREAMS)
set(DRM_SOURCES ${DRM_SOURCES}
egl_stream_backend.cpp
)
endif()
add_library(KWinWaylandDrmBackend MODULE ${DRM_SOURCES})
set_target_properties(KWinWaylandDrmBackend PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/org.kde.kwin.waylandbackends/")
target_link_libraries(KWinWaylandDrmBackend kwin Libdrm::Libdrm)

View file

@ -31,9 +31,6 @@
#include <gbm.h>
#include "gbm_dmabuf.h"
#endif
#if HAVE_EGL_STREAMS
#include "egl_stream_backend.h"
#endif
// KF5
#include <KCoreAddons>
#include <KLocalizedString>
@ -190,17 +187,6 @@ bool DrmBackend::initialize()
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;
}
}
}
}
if (m_gpus.isEmpty()) {
@ -209,10 +195,6 @@ bool DrmBackend::initialize()
}
initCursor();
// workaround for BUG 438363: something goes wrong in scene initialization without a surface being current in EglStreamBackend
if (m_gpus[0]->useEglStreams()) {
updateOutputs();
}
// setup udevMonitor
if (m_udevMonitor) {
@ -276,9 +258,6 @@ 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;
@ -519,21 +498,6 @@ void DrmBackend::enableOutput(DrmAbstractOutput *output, bool enable)
void DrmBackend::initCursor()
{
#if HAVE_EGL_STREAMS
// Hardware cursors aren't currently supported with EGLStream backend,
// possibly an NVIDIA driver bug
bool needsSoftwareCursor = false;
for (auto gpu : qAsConst(m_gpus)) {
if (gpu->useEglStreams()) {
needsSoftwareCursor = true;
break;
}
}
setSoftwareCursorForced(needsSoftwareCursor);
#endif
// now we have screens and can set cursors, so start tracking
connect(Cursors::self(), &Cursors::currentCursorChanged, this, &DrmBackend::updateCursor);
connect(Cursors::self(), &Cursors::positionChanged, this, &DrmBackend::moveCursor);
}
@ -623,14 +587,6 @@ QPainterBackend *DrmBackend::createQPainterBackend()
OpenGLBackend *DrmBackend::createOpenGLBackend()
{
#if HAVE_EGL_STREAMS
if (m_gpus.at(0)->useEglStreams()) {
auto backend = new EglStreamBackend(this, m_gpus.at(0));
AbstractEglBackend::setPrimaryBackend(backend);
return backend;
}
#endif
#if HAVE_GBM
auto primaryBackend = new EglGbmBackend(this, m_gpus.at(0));
AbstractEglBackend::setPrimaryBackend(primaryBackend);
@ -656,10 +612,6 @@ QVector<CompositingType> DrmBackend::supportedCompositors() const
}
#if HAVE_GBM
return QVector<CompositingType>{OpenGLCompositing, QPainterCompositing};
#elif HAVE_EGL_STREAMS
return m_gpus.at(0)->useEglStreams() ?
QVector<CompositingType>{OpenGLCompositing, QPainterCompositing} :
QVector<CompositingType>{QPainterCompositing};
#else
return QVector<CompositingType>{QPainterCompositing};
#endif
@ -675,9 +627,6 @@ QString DrmBackend::supportInformation() const
for (int g = 0; g < m_gpus.size(); g++) {
s << "Atomic Mode Setting on GPU " << g << ": " << m_gpus.at(g)->atomicModeSetting() << Qt::endl;
}
#if HAVE_EGL_STREAMS
s << "Using EGL Streams: " << m_gpus.at(0)->useEglStreams() << Qt::endl;
#endif
return supportInfo;
}

View file

@ -80,16 +80,8 @@ DrmGpu::DrmGpu(DrmBackend *backend, const QString &devNode, int fd, dev_t device
// find out if this GPU is using the NVidia proprietary driver
DrmScopedPointer<drmVersion> version(drmGetVersion(fd));
m_isNVidia = strstr(version->name, "nvidia-drm");
m_useEglStreams = m_isNVidia;
#if HAVE_GBM
m_gbmDevice = gbm_create_device(m_fd);
bool envVarIsSet = false;
bool value = qEnvironmentVariableIntValue("KWIN_DRM_FORCE_EGL_STREAMS", &envVarIsSet) != 0;
if (envVarIsSet) {
m_useEglStreams = m_isNVidia && value;
} else if (m_gbmDevice) {
m_useEglStreams = m_isNVidia && strcmp(gbm_device_get_backend_name(m_gbmDevice), "nvidia") != 0;
}
#endif
m_socketNotifier = new QSocketNotifier(fd, QSocketNotifier::Read, this);
@ -695,11 +687,6 @@ bool DrmGpu::atomicModeSetting() const
return m_atomicModeSetting;
}
bool DrmGpu::useEglStreams() const
{
return m_useEglStreams;
}
QString DrmGpu::devNode() const
{
return m_devNode;

View file

@ -54,7 +54,6 @@ public:
bool atomicModeSetting() const;
bool addFB2ModifiersSupported() const;
bool useEglStreams() const;
bool isNVidia() const;
bool isFormatSupported(uint32_t drmFormat) const;
gbm_device *gbmDevice() const;
@ -112,7 +111,6 @@ private:
const dev_t m_deviceId;
const QString m_devNode;
bool m_atomicModeSetting;
bool m_useEglStreams;
bool m_addFB2ModifiersSupported = false;
bool m_isNVidia;
clockid_t m_presentationClock;

View file

@ -62,13 +62,6 @@ bool DrmPipeline::present(const QSharedPointer<DrmBuffer> &buffer)
return false;
}
m_primaryBuffer = buffer;
if (gpu()->useEglStreams() && gpu()->eglBackend() != nullptr && gpu() == gpu()->platform()->primaryGpu()) {
// EglStreamBackend queues normal page flips through EGL,
// modesets etc are performed through DRM-KMS
if (!m_connector->needsCommit() && !pending.crtc->needsCommit()) {
return true;
}
}
bool directScanout = false;
#if HAVE_GBM
// with direct scanout disallow modesets, calling presentFailed() and logging warnings
@ -254,8 +247,7 @@ bool DrmPipeline::populateAtomicValues(drmModeAtomicReq *req, uint32_t &flags)
prepareModeset();
flags |= DRM_MODE_ATOMIC_ALLOW_MODESET;
}
bool usesEglStreams = gpu()->useEglStreams() && gpu()->eglBackend() != nullptr && gpu() == gpu()->platform()->primaryGpu();
if (!usesEglStreams && activePending()) {
if (activePending()) {
flags |= DRM_MODE_PAGE_FLIP_EVENT;
}
if (pending.crtc) {

View file

@ -13,9 +13,6 @@
#if HAVE_GBM
#include "egl_gbm_backend.h"
#endif
#if HAVE_EGL_STREAMS
#include "egl_stream_backend.h"
#endif
#include "drm_backend.h"
#include "drm_gpu.h"
@ -115,22 +112,13 @@ bool EglMultiBackend::directScanoutAllowed(AbstractOutput *output) const
void EglMultiBackend::addGpu(DrmGpu *gpu)
{
AbstractEglDrmBackend *backend;
if (gpu->useEglStreams()) {
#if HAVE_EGL_STREAMS
backend = new EglStreamBackend(m_platform, gpu);
#endif
} else {
#if HAVE_GBM
backend = new EglGbmBackend(m_platform, gpu);
AbstractEglDrmBackend *backend= new EglGbmBackend(m_platform, gpu);
if (m_initialized) {
backend->init();
}
m_backends.append(backend);
#endif
}
if (backend) {
if (m_initialized) {
backend->init();
}
m_backends.append(backend);
}
}
void EglMultiBackend::removeGpu(DrmGpu *gpu)

View file

@ -1,783 +0,0 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2019 NVIDIA Inc.
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "egl_stream_backend.h"
#include "basiceglsurfacetexture_internal.h"
#include "composite.h"
#include "drm_backend.h"
#include "drm_output.h"
#include "drm_object_crtc.h"
#include "drm_object_plane.h"
#include "logging.h"
#include "options.h"
#include "renderloop_p.h"
#include "scene.h"
#include "screens.h"
#include "surfaceitem_wayland.h"
#include "wayland_server.h"
#include <kwinglplatform.h>
#include <kwingltexture.h>
#include "drm_gpu.h"
#include "dumb_swapchain.h"
#include "kwineglutils_p.h"
#include "shadowbuffer.h"
#include "drm_pipeline.h"
#include <QOpenGLContext>
#include <KWaylandServer/clientbuffer.h>
#include <KWaylandServer/display.h>
#include <KWaylandServer/eglstream_controller_interface.h>
#include <drm_fourcc.h>
namespace KWin
{
typedef EGLStreamKHR (*PFNEGLCREATESTREAMATTRIBNV)(EGLDisplay, EGLAttrib *);
typedef EGLBoolean (*PFNEGLGETOUTPUTLAYERSEXT)(EGLDisplay, EGLAttrib *, EGLOutputLayerEXT *, EGLint, EGLint *);
typedef EGLBoolean (*PFNEGLSTREAMCONSUMEROUTPUTEXT)(EGLDisplay, EGLStreamKHR, EGLOutputLayerEXT);
typedef EGLSurface (*PFNEGLCREATESTREAMPRODUCERSURFACEKHR)(EGLDisplay, EGLConfig, EGLStreamKHR, EGLint *);
typedef EGLBoolean (*PFNEGLDESTROYSTREAMKHR)(EGLDisplay, EGLStreamKHR);
typedef EGLBoolean (*PFNEGLSTREAMCONSUMERACQUIREATTRIBNV)(EGLDisplay, EGLStreamKHR, EGLAttrib *);
typedef EGLBoolean (*PFNEGLSTREAMCONSUMERGLTEXTUREEXTERNALKHR)(EGLDisplay, EGLStreamKHR);
typedef EGLBoolean (*PFNEGLQUERYSTREAMATTRIBNV)(EGLDisplay, EGLStreamKHR, EGLenum, EGLAttrib *);
typedef EGLBoolean (*PFNEGLSTREAMCONSUMERRELEASEKHR)(EGLDisplay, EGLStreamKHR);
typedef EGLBoolean (*PFNEGLQUERYWAYLANDBUFFERWL)(EGLDisplay, wl_resource *, EGLint, EGLint *);
PFNEGLCREATESTREAMATTRIBNV pEglCreateStreamAttribNV = nullptr;
PFNEGLGETOUTPUTLAYERSEXT pEglGetOutputLayersEXT = nullptr;
PFNEGLSTREAMCONSUMEROUTPUTEXT pEglStreamConsumerOutputEXT = nullptr;
PFNEGLCREATESTREAMPRODUCERSURFACEKHR pEglCreateStreamProducerSurfaceKHR = nullptr;
PFNEGLDESTROYSTREAMKHR pEglDestroyStreamKHR = nullptr;
PFNEGLSTREAMCONSUMERACQUIREATTRIBNV pEglStreamConsumerAcquireAttribNV = nullptr;
PFNEGLSTREAMCONSUMERGLTEXTUREEXTERNALKHR pEglStreamConsumerGLTextureExternalKHR = nullptr;
PFNEGLQUERYSTREAMATTRIBNV pEglQueryStreamAttribNV = nullptr;
PFNEGLSTREAMCONSUMERRELEASEKHR pEglStreamConsumerReleaseKHR = nullptr;
PFNEGLQUERYWAYLANDBUFFERWL pEglQueryWaylandBufferWL = nullptr;
#ifndef EGL_CONSUMER_AUTO_ACQUIRE_EXT
#define EGL_CONSUMER_AUTO_ACQUIRE_EXT 0x332B
#endif
#ifndef EGL_DRM_MASTER_FD_EXT
#define EGL_DRM_MASTER_FD_EXT 0x333C
#endif
#ifndef EGL_DRM_FLIP_EVENT_DATA_NV
#define EGL_DRM_FLIP_EVENT_DATA_NV 0x333E
#endif
#ifndef EGL_WAYLAND_EGLSTREAM_WL
#define EGL_WAYLAND_EGLSTREAM_WL 0x334B
#endif
#ifndef EGL_WAYLAND_Y_INVERTED_WL
#define EGL_WAYLAND_Y_INVERTED_WL 0x31DB
#endif
EglStreamBackend::EglStreamBackend(DrmBackend *drmBackend, DrmGpu *gpu)
: AbstractEglDrmBackend(drmBackend, gpu)
{
}
EglStreamBackend::~EglStreamBackend()
{
cleanup();
}
void EglStreamBackend::cleanupSurfaces()
{
for (auto it = m_outputs.begin(); it != m_outputs.end(); ++it) {
cleanupOutput(*it);
}
m_outputs.clear();
}
void EglStreamBackend::cleanupOutput(Output &o)
{
if (o.eglSurface != EGL_NO_SURFACE) {
eglDestroySurface(eglDisplay(), o.eglSurface);
}
if (o.eglStream != EGL_NO_STREAM_KHR) {
pEglDestroyStreamKHR(eglDisplay(), o.eglStream);
}
o.shadowBuffer = nullptr;
}
bool EglStreamBackend::initializeEgl()
{
initClientExtensions();
EGLDisplay display = m_gpu->eglDisplay();
if (display == EGL_NO_DISPLAY) {
if (!hasClientExtension(QByteArrayLiteral("EGL_EXT_device_base")) &&
!(hasClientExtension(QByteArrayLiteral("EGL_EXT_device_query")) &&
hasClientExtension(QByteArrayLiteral("EGL_EXT_device_enumeration")))) {
setFailed("Missing required EGL client extension: "
"EGL_EXT_device_base or "
"EGL_EXT_device_query and EGL_EXT_device_enumeration");
return false;
}
// Try to find the EGLDevice corresponding to our DRM device file
int numDevices;
eglQueryDevicesEXT(0, nullptr, &numDevices);
QVector<EGLDeviceEXT> devices(numDevices);
eglQueryDevicesEXT(numDevices, devices.data(), &numDevices);
for (EGLDeviceEXT device : devices) {
const char *drmDeviceFile = eglQueryDeviceStringEXT(device, EGL_DRM_DEVICE_FILE_EXT);
if (m_gpu->devNode().compare(drmDeviceFile)) {
continue;
}
const char *deviceExtensionCString = eglQueryDeviceStringEXT(device, EGL_EXTENSIONS);
QByteArray deviceExtensions = QByteArray::fromRawData(deviceExtensionCString,
qstrlen(deviceExtensionCString));
if (!deviceExtensions.split(' ').contains(QByteArrayLiteral("EGL_EXT_device_drm"))) {
continue;
}
EGLint platformAttribs[] = {
EGL_DRM_MASTER_FD_EXT, m_gpu->fd(),
EGL_NONE
};
display = eglGetPlatformDisplayEXT(EGL_PLATFORM_DEVICE_EXT, device, platformAttribs);
break;
}
m_gpu->setEglDisplay(display);
}
if (display == EGL_NO_DISPLAY) {
setFailed("No suitable EGL device found");
return false;
}
setEglDisplay(display);
if (!initEglAPI()) {
return false;
}
const QVector<QByteArray> requiredExtensions = {
QByteArrayLiteral("EGL_EXT_output_base"),
QByteArrayLiteral("EGL_EXT_output_drm"),
QByteArrayLiteral("EGL_KHR_stream"),
QByteArrayLiteral("EGL_KHR_stream_producer_eglsurface"),
QByteArrayLiteral("EGL_EXT_stream_consumer_egloutput"),
QByteArrayLiteral("EGL_NV_stream_attrib"),
QByteArrayLiteral("EGL_EXT_stream_acquire_mode"),
QByteArrayLiteral("EGL_KHR_stream_consumer_gltexture"),
QByteArrayLiteral("EGL_WL_wayland_eglstream")
};
for (const QByteArray &ext : requiredExtensions) {
if (!hasExtension(ext)) {
setFailed(QStringLiteral("Missing required EGL extension: ") + ext);
return false;
}
}
pEglCreateStreamAttribNV = (PFNEGLCREATESTREAMATTRIBNV)eglGetProcAddress("eglCreateStreamAttribNV");
pEglGetOutputLayersEXT = (PFNEGLGETOUTPUTLAYERSEXT)eglGetProcAddress("eglGetOutputLayersEXT");
pEglStreamConsumerOutputEXT = (PFNEGLSTREAMCONSUMEROUTPUTEXT)eglGetProcAddress("eglStreamConsumerOutputEXT");
pEglCreateStreamProducerSurfaceKHR = (PFNEGLCREATESTREAMPRODUCERSURFACEKHR)eglGetProcAddress("eglCreateStreamProducerSurfaceKHR");
pEglDestroyStreamKHR = (PFNEGLDESTROYSTREAMKHR)eglGetProcAddress("eglDestroyStreamKHR");
pEglStreamConsumerAcquireAttribNV = (PFNEGLSTREAMCONSUMERACQUIREATTRIBNV)eglGetProcAddress("eglStreamConsumerAcquireAttribNV");
pEglStreamConsumerGLTextureExternalKHR = (PFNEGLSTREAMCONSUMERGLTEXTUREEXTERNALKHR)eglGetProcAddress("eglStreamConsumerGLTextureExternalKHR");
pEglQueryStreamAttribNV = (PFNEGLQUERYSTREAMATTRIBNV)eglGetProcAddress("eglQueryStreamAttribNV");
pEglStreamConsumerReleaseKHR = (PFNEGLSTREAMCONSUMERRELEASEKHR)eglGetProcAddress("eglStreamConsumerReleaseKHR");
pEglQueryWaylandBufferWL = (PFNEGLQUERYWAYLANDBUFFERWL)eglGetProcAddress("eglQueryWaylandBufferWL");
return true;
}
EglStreamBackend::StreamTexture *EglStreamBackend::lookupStreamTexture(KWaylandServer::SurfaceInterface *surface)
{
auto it = m_streamTextures.find(surface);
return it != m_streamTextures.end() ?
&it.value() :
nullptr;
}
void EglStreamBackend::destroyStreamTexture(KWaylandServer::SurfaceInterface *surface)
{
const StreamTexture &st = m_streamTextures.take(surface);
pEglDestroyStreamKHR(eglDisplay(), st.stream);
glDeleteTextures(1, &st.texture);
}
void EglStreamBackend::attachStreamConsumer(KWaylandServer::SurfaceInterface *surface,
void *eglStream,
wl_array *attribs)
{
makeCurrent();
QVector<EGLAttrib> streamAttribs;
streamAttribs << EGL_WAYLAND_EGLSTREAM_WL << (EGLAttrib)eglStream;
EGLAttrib *attribArray = (EGLAttrib *)attribs->data;
for (unsigned int i = 0; i < attribs->size; ++i) {
streamAttribs << attribArray[i];
}
streamAttribs << EGL_NONE;
EGLStreamKHR stream = pEglCreateStreamAttribNV(eglDisplay(), streamAttribs.data());
if (stream == EGL_NO_STREAM_KHR) {
qCWarning(KWIN_DRM) << "Failed to create EGL stream:" << getEglErrorString();
return;
}
GLuint texture;
StreamTexture *st = lookupStreamTexture(surface);
if (st != nullptr) {
pEglDestroyStreamKHR(eglDisplay(), st->stream);
st->stream = stream;
texture = st->texture;
} else {
StreamTexture newSt = { stream, 0 };
glGenTextures(1, &newSt.texture);
m_streamTextures.insert(surface, newSt);
texture = newSt.texture;
connect(surface, &KWaylandServer::SurfaceInterface::destroyed, this,
[surface, this]() {
makeCurrent();
destroyStreamTexture(surface);
});
}
glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture);
if (!pEglStreamConsumerGLTextureExternalKHR(eglDisplay(), stream)) {
qCWarning(KWIN_DRM) << "Failed to bind EGL stream to texture:" << getEglErrorString();
}
glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0);
}
void EglStreamBackend::init()
{
if (!m_gpu->atomicModeSetting()) {
setFailed("EGLStream backend requires atomic modesetting");
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();
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 (DrmAbstractOutput *drmOutput : outputs) {
addOutput(drmOutput);
}
}
}
bool EglStreamBackend::initRenderingContext()
{
initBufferConfigs();
if (!createContext()) {
return false;
}
const auto outputs = m_gpu->outputs();
for (DrmAbstractOutput *drmOutput : outputs) {
addOutput(drmOutput);
}
return !m_outputs.isEmpty() && makeContextCurrent(m_outputs.first());
}
bool EglStreamBackend::resetOutput(Output &o)
{
const auto &drmOutput = o.output;
QSize sourceSize = drmOutput->sourceSize();
if (isPrimary()) {
// dumb buffer used for modesetting
o.buffer = QSharedPointer<DrmDumbBuffer>::create(m_gpu, sourceSize);
o.targetPlane = drmOutput->pipeline()->pending.crtc->primaryPlane();
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:" << getEglErrorString();
return false;
}
EGLAttrib outputAttribs[3];
outputAttribs[0] = EGL_DRM_PLANE_EXT;
outputAttribs[1] = o.targetPlane->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, sourceSize.width(),
EGL_HEIGHT, sourceSize.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:" << getEglErrorString();
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;
if (drmOutput->needsSoftwareTransformation()) {
makeContextCurrent(o);
o.shadowBuffer = QSharedPointer<ShadowBuffer>::create(o.output->pixelSize());
if (!o.shadowBuffer->isComplete()) {
cleanupOutput(o);
return false;
}
}
} else {
o.dumbSwapchain = QSharedPointer<DumbSwapchain>::create(m_gpu, sourceSize);
if (o.dumbSwapchain->isEmpty()) {
return false;
}
}
return true;
}
bool EglStreamBackend::addOutput(DrmAbstractOutput *output)
{
Q_ASSERT(output->gpu() == m_gpu);
DrmOutput *drmOutput = qobject_cast<DrmOutput *>(output);
if (drmOutput) {
Output o;
o.output = drmOutput;
if (!resetOutput(o)) {
return false;
}
if (!isPrimary() && !renderingBackend()->addOutput(drmOutput)) {
return false;
}
m_outputs.insert(output, o);
return true;
} else {
return false;
}
}
void EglStreamBackend::removeOutput(DrmAbstractOutput *drmOutput)
{
Q_ASSERT(drmOutput->gpu() == m_gpu);
auto it = std::find_if(m_outputs.begin(), m_outputs.end(),
[drmOutput] (const Output &o) {
return o.output == drmOutput;
}
);
if (it == m_outputs.end()) {
return;
}
cleanupOutput(*it);
m_outputs.erase(it);
if (!isPrimary()) {
renderingBackend()->removeOutput(drmOutput);
}
}
bool EglStreamBackend::makeContextCurrent(const Output &output)
{
const EGLSurface surface = output.eglSurface;
if (surface == EGL_NO_SURFACE) {
return false;
}
if (eglMakeCurrent(eglDisplay(), surface, surface, context()) == EGL_FALSE) {
qCCritical(KWIN_DRM) << "Failed to make EGL context current:" << getEglErrorString();
return false;
}
EGLint error = eglGetError();
if (error != EGL_SUCCESS) {
qCWarning(KWIN_DRM) << "Error occurred while making EGL context current:" << getEglErrorString(error);
return false;
}
const QSize size = output.output->pixelSize();
glViewport(0, 0, size.width(), size.height());
return true;
}
bool EglStreamBackend::initBufferConfigs()
{
const EGLint configAttribs[] = {
EGL_SURFACE_TYPE, EGL_STREAM_BIT_KHR,
EGL_RED_SIZE, 1,
EGL_GREEN_SIZE, 1,
EGL_BLUE_SIZE, 1,
EGL_ALPHA_SIZE, 0,
EGL_RENDERABLE_TYPE, isOpenGLES() ? EGL_OPENGL_ES2_BIT : EGL_OPENGL_BIT,
EGL_CONFIG_CAVEAT, EGL_NONE,
EGL_NONE,
};
EGLint count;
EGLConfig config;
if (!eglChooseConfig(eglDisplay(), configAttribs, &config, 1, &count)) {
qCCritical(KWIN_DRM) << "Failed to query available EGL configs:" << getEglErrorString();
return false;
}
if (count == 0) {
qCCritical(KWIN_DRM) << "No suitable EGL config found";
return false;
}
setConfig(config);
return true;
}
SurfaceTexture *EglStreamBackend::createSurfaceTextureInternal(SurfacePixmapInternal *pixmap)
{
return new BasicEGLSurfaceTextureInternal(this, pixmap);
}
SurfaceTexture *EglStreamBackend::createSurfaceTextureWayland(SurfacePixmapWayland *pixmap)
{
return new EglStreamSurfaceTextureWayland(this, pixmap);
}
bool EglStreamBackend::needsReset(const Output &o) const
{
if (o.targetPlane != o.output->pipeline()->pending.crtc->primaryPlane()) {
return true;
}
QSize surfaceSize = o.dumbSwapchain ? o.dumbSwapchain->size() : o.buffer->size();
if (surfaceSize != o.output->sourceSize()) {
return true;
}
bool needsTexture = o.output->needsSoftwareTransformation();
if (needsTexture) {
return !o.shadowBuffer || o.shadowBuffer->textureSize() != o.output->pixelSize();
} else {
return o.shadowBuffer != nullptr;
}
}
QRegion EglStreamBackend::beginFrame(AbstractOutput *drmOutput)
{
Q_ASSERT(m_outputs.contains(drmOutput));
Output &o = m_outputs[drmOutput];
if (isPrimary()) {
if (needsReset(o)) {
if (!resetOutput(o)) {
// handle this better?
return {};
}
}
makeContextCurrent(o);
if (o.shadowBuffer) {
o.shadowBuffer->bind();
}
return o.output->geometry();
} else {
return renderingBackend()->beginFrameForSecondaryGpu(o.output);
}
}
void EglStreamBackend::endFrame(AbstractOutput *output, const QRegion &renderedRegion, const QRegion &damagedRegion)
{
Q_ASSERT(m_outputs.contains(output));
Q_UNUSED(renderedRegion);
Output &renderOutput = m_outputs[output];
bool frameFailed = false;
QSharedPointer<DrmDumbBuffer> buffer;
if (isPrimary()) {
buffer = renderOutput.buffer;
if (renderOutput.shadowBuffer) {
renderOutput.shadowBuffer->render(renderOutput.output);
}
if (!eglSwapBuffers(eglDisplay(), renderOutput.eglSurface)) {
qCCritical(KWIN_DRM) << "eglSwapBuffers() failed:" << getEglErrorString();
frameFailed = true;
}
} else {
if (!renderingBackend()->swapBuffers(static_cast<DrmOutput*>(output), damagedRegion.intersected(output->geometry()))) {
qCCritical(KWIN_DRM) << "swapping buffers on render backend for" << output << "failed!";
frameFailed = true;
}
buffer = renderOutput.dumbSwapchain->acquireBuffer();
if (!frameFailed && !renderingBackend()->exportFramebuffer(static_cast<DrmOutput*>(output), buffer->data(), buffer->size(), buffer->stride())) {
qCCritical(KWIN_DRM) << "importing framebuffer from render backend for" << output << "failed!";
frameFailed = true;
}
}
if (!frameFailed && !renderOutput.output->present(buffer, damagedRegion)) {
frameFailed = true;
}
if (frameFailed) {
RenderLoopPrivate *renderLoopPrivate = RenderLoopPrivate::get(output->renderLoop());
renderLoopPrivate->notifyFrameFailed();
} else if (isPrimary()) {
EGLAttrib acquireAttribs[] = {
EGL_DRM_FLIP_EVENT_DATA_NV, (EGLAttrib)output,
EGL_NONE,
};
if (!pEglStreamConsumerAcquireAttribNV(eglDisplay(), renderOutput.eglStream, acquireAttribs)) {
qCWarning(KWIN_DRM) << "Failed to acquire output EGL stream frame:" << getEglErrorString();
}
}
}
QSharedPointer<DrmBuffer> EglStreamBackend::renderTestFrame(DrmAbstractOutput *drmOutput)
{
Q_ASSERT(m_outputs.contains(drmOutput));
auto &output = m_outputs[drmOutput];
auto buffer = output.dumbSwapchain ? output.dumbSwapchain->currentBuffer() : output.buffer;
auto size = drmOutput->sourceSize();
if (buffer->size() == size) {
return buffer;
} else {
return QSharedPointer<DrmDumbBuffer>::create(m_gpu, size);
}
}
bool EglStreamBackend::hasOutput(AbstractOutput *output) const
{
return m_outputs.contains(output);
}
uint32_t EglStreamBackend::drmFormat() const
{
return DRM_FORMAT_XRGB8888;
}
/************************************************
* EglTexture
************************************************/
EglStreamSurfaceTextureWayland::EglStreamSurfaceTextureWayland(EglStreamBackend *backend,
SurfacePixmapWayland *pixmap)
: BasicEGLSurfaceTextureWayland(backend, pixmap)
, m_backend(backend)
{
}
EglStreamSurfaceTextureWayland::~EglStreamSurfaceTextureWayland()
{
glDeleteRenderbuffers(1, &m_rbo);
glDeleteFramebuffers(1, &m_fbo);
glDeleteTextures(1, &m_textureId);
}
bool EglStreamSurfaceTextureWayland::acquireStreamFrame(EGLStreamKHR stream)
{
EGLAttrib streamState;
if (!pEglQueryStreamAttribNV(m_backend->eglDisplay(), stream,
EGL_STREAM_STATE_KHR, &streamState)) {
qCWarning(KWIN_DRM) << "Failed to query EGL stream state:" << getEglErrorString();
return false;
}
if (streamState == EGL_STREAM_STATE_NEW_FRAME_AVAILABLE_KHR) {
if (pEglStreamConsumerAcquireAttribNV(m_backend->eglDisplay(), stream, nullptr)) {
return true;
} else {
qCWarning(KWIN_DRM) << "Failed to acquire EGL stream frame:" << getEglErrorString();
}
}
// Re-use previous texture contents if no new frame is available
// or if acquisition fails for some reason
return false;
}
void EglStreamSurfaceTextureWayland::createFbo()
{
glDeleteRenderbuffers(1, &m_rbo);
glDeleteFramebuffers(1, &m_fbo);
GLuint oldReadFbo = 0;
GLuint oldDrawFbo = 0;
glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, reinterpret_cast<GLint *>(&oldReadFbo));
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, reinterpret_cast<GLint *>(&oldDrawFbo));
glGenFramebuffers(1, &m_fbo);
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
glGenRenderbuffers(1, &m_rbo);
glBindRenderbuffer(GL_RENDERBUFFER, m_rbo);
glRenderbufferStorage(GL_RENDERBUFFER, m_format, m_texture->width(), m_texture->height());
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_rbo);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, oldReadFbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, oldDrawFbo);
}
// Renders the contents of the given EXTERNAL_OES texture
// to the scratch framebuffer, then copies this to m_texture
void EglStreamSurfaceTextureWayland::copyExternalTexture(GLuint tex)
{
GLint oldViewport[4], oldProgram, oldReadFbo, oldDrawFbo;
glGetIntegerv(GL_VIEWPORT, oldViewport);
glViewport(0, 0, m_texture->width(), m_texture->height());
glGetIntegerv(GL_CURRENT_PROGRAM, &oldProgram);
glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, reinterpret_cast<GLint *>(&oldReadFbo));
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, reinterpret_cast<GLint *>(&oldDrawFbo));
glUseProgram(0);
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
glBindRenderbuffer(GL_RENDERBUFFER, m_rbo);
glBindTexture(GL_TEXTURE_EXTERNAL_OES, tex);
glEnable(GL_TEXTURE_EXTERNAL_OES);
GLfloat yTop = texture()->isYInverted() ? 0 : 1;
glBegin(GL_QUADS);
glTexCoord2f(0, yTop);
glVertex2f(-1, 1);
glTexCoord2f(0, 1 - yTop);
glVertex2f(-1, -1);
glTexCoord2f(1, 1 - yTop);
glVertex2f(1, -1);
glTexCoord2f(1, yTop);
glVertex2f(1, 1);
glEnd();
texture()->bind();
glCopyTexImage2D(m_texture->target(), 0, m_format, 0, 0, m_texture->width(), m_texture->height(), 0);
texture()->unbind();
glDisable(GL_TEXTURE_EXTERNAL_OES);
glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, oldReadFbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, oldDrawFbo);
glUseProgram(oldProgram);
glViewport(oldViewport[0], oldViewport[1], oldViewport[2], oldViewport[3]);
}
bool EglStreamSurfaceTextureWayland::attachBuffer(KWaylandServer::ClientBuffer *buffer)
{
GLenum oldFormat = m_format;
m_format = buffer->hasAlphaChannel() ? GL_RGBA : GL_RGB;
EGLint yInverted, wasYInverted = texture()->isYInverted();
if (!pEglQueryWaylandBufferWL(m_backend->eglDisplay(), buffer->resource(), EGL_WAYLAND_Y_INVERTED_WL, &yInverted)) {
yInverted = EGL_TRUE;
}
texture()->setYInverted(yInverted);
return oldFormat != m_format || wasYInverted != texture()->isYInverted();
}
bool EglStreamSurfaceTextureWayland::checkBuffer(KWaylandServer::SurfaceInterface *surface,
KWaylandServer::ClientBuffer *buffer)
{
EGLAttrib attribs[] = {
EGL_WAYLAND_EGLSTREAM_WL, (EGLAttrib)buffer->resource(),
EGL_NONE
};
EGLStreamKHR stream = pEglCreateStreamAttribNV(m_backend->eglDisplay(), attribs);
if (stream == EGL_NO_STREAM_KHR) {
// eglCreateStreamAttribNV generates EGL_BAD_ACCESS if the
// provided buffer is not a wl_eglstream. In that case, clean up
// the old stream and fall back to the dmabuf or shm attach
// paths.
EGLint err = eglGetError();
if (err == EGL_BAD_ACCESS) {
m_backend->destroyStreamTexture(surface);
return false;
}
// Otherwise it should have generated EGL_BAD_STREAM_KHR since
// we've already created an EGLStream for it.
Q_ASSERT(err == EGL_BAD_STREAM_KHR);
} else {
// If eglCreateStreamAttribNV *didn't* fail, that means the
// buffer is a wl_eglstream but it hasn't been attached to a
// consumer for some reason. Not much we can do here.
qCCritical(KWIN_DRM) << "Untracked wl_eglstream attached to surface";
pEglDestroyStreamKHR(m_backend->eglDisplay(), stream);
}
return true;
}
bool EglStreamSurfaceTextureWayland::create()
{
using namespace KWaylandServer;
SurfaceInterface *surface = m_pixmap->surface();
const EglStreamBackend::StreamTexture *st = m_backend->lookupStreamTexture(surface);
if (m_pixmap->buffer() && st != nullptr && checkBuffer(surface, m_pixmap->buffer())) {
glGenTextures(1, &m_textureId);
m_texture.reset(new GLTexture(m_textureId, 0, m_pixmap->buffer()->size()));
m_texture->setWrapMode(GL_CLAMP_TO_EDGE);
m_texture->setFilter(GL_LINEAR);
attachBuffer(surface->buffer());
createFbo();
if (acquireStreamFrame(st->stream)) {
copyExternalTexture(st->texture);
if (!pEglStreamConsumerReleaseKHR(m_backend->eglDisplay(), st->stream)) {
qCWarning(KWIN_DRM) << "Failed to release EGL stream:" << getEglErrorString();
}
}
return true;
} else {
// Not an EGLStream surface
return BasicEGLSurfaceTextureWayland::create();
}
}
void EglStreamSurfaceTextureWayland::update(const QRegion &region)
{
using namespace KWaylandServer;
SurfaceInterface *surface = m_pixmap->surface();
const EglStreamBackend::StreamTexture *st = m_backend->lookupStreamTexture(surface);
if (m_pixmap->buffer() && st != nullptr && checkBuffer(surface, m_pixmap->buffer())) {
if (attachBuffer(surface->buffer())) {
createFbo();
}
if (acquireStreamFrame(st->stream)) {
copyExternalTexture(st->texture);
if (!pEglStreamConsumerReleaseKHR(m_backend->eglDisplay(), st->stream)) {
qCWarning(KWIN_DRM) << "Failed to release EGL stream:" << getEglErrorString();
}
}
} else {
// Not an EGLStream surface
BasicEGLSurfaceTextureWayland::update(region);
}
}
} // namespace

View file

@ -1,116 +0,0 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2019 NVIDIA Inc.
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef KWIN_EGL_STREAM_BACKEND_H
#define KWIN_EGL_STREAM_BACKEND_H
#include "abstract_egl_drm_backend.h"
#include "basiceglsurfacetexture_wayland.h"
#include <KWaylandServer/surface_interface.h>
#include <KWaylandServer/eglstream_controller_interface.h>
#include <wayland-server-core.h>
namespace KWin
{
class DrmAbstractOutput;
class DrmDumbBuffer;
class DumbSwapchain;
class ShadowBuffer;
class DrmCrtc;
class DrmPlane;
/**
* @brief OpenGL Backend using Egl with an EGLDevice.
*/
class EglStreamBackend : public AbstractEglDrmBackend
{
Q_OBJECT
public:
EglStreamBackend(DrmBackend *b, DrmGpu *gpu);
~EglStreamBackend() override;
SurfaceTexture *createSurfaceTextureInternal(SurfacePixmapInternal *pixmap) override;
SurfaceTexture *createSurfaceTextureWayland(SurfacePixmapWayland *pixmap) override;
QRegion beginFrame(AbstractOutput *output) override;
void endFrame(AbstractOutput *output, const QRegion &damage, const QRegion &damagedRegion) override;
void init() override;
bool hasOutput(AbstractOutput *output) const override;
bool addOutput(DrmAbstractOutput *output) override;
void removeOutput(DrmAbstractOutput *output) override;
QSharedPointer<DrmBuffer> renderTestFrame(DrmAbstractOutput *output) override;
uint32_t drmFormat() const override;
protected:
void cleanupSurfaces() override;
private:
bool initializeEgl();
bool initBufferConfigs();
bool initRenderingContext();
struct StreamTexture
{
EGLStreamKHR stream;
GLuint texture;
};
StreamTexture *lookupStreamTexture(KWaylandServer::SurfaceInterface *surface);
void destroyStreamTexture(KWaylandServer::SurfaceInterface *surface);
void attachStreamConsumer(KWaylandServer::SurfaceInterface *surface,
void *eglStream,
wl_array *attribs);
struct Output
{
DrmOutput *output = nullptr;
QSharedPointer<DrmDumbBuffer> buffer;
EGLSurface eglSurface = EGL_NO_SURFACE;
EGLStreamKHR eglStream = EGL_NO_STREAM_KHR;
QSharedPointer<ShadowBuffer> shadowBuffer;
DrmPlane *targetPlane = nullptr;
// for operation as secondary GPU
QSharedPointer<DumbSwapchain> dumbSwapchain;
};
bool resetOutput(Output &output);
bool createEglSurface(Output &o);
bool makeContextCurrent(const Output &output);
void cleanupOutput(Output &output);
bool needsReset(const Output &o) const;
QMap<AbstractOutput *, Output> m_outputs;
KWaylandServer::EglStreamControllerInterface *m_eglStreamControllerInterface;
QHash<KWaylandServer::SurfaceInterface *, StreamTexture> m_streamTextures;
friend class EglStreamSurfaceTextureWayland;
};
class EglStreamSurfaceTextureWayland : public BasicEGLSurfaceTextureWayland
{
public:
EglStreamSurfaceTextureWayland(EglStreamBackend *backend, SurfacePixmapWayland *pixmap);
~EglStreamSurfaceTextureWayland() override;
bool create() override;
void update(const QRegion &region) override;
private:
bool acquireStreamFrame(EGLStreamKHR stream);
void createFbo();
void copyExternalTexture(GLuint tex);
bool attachBuffer(KWaylandServer::ClientBuffer *buffer);
bool checkBuffer(KWaylandServer::SurfaceInterface *surface,
KWaylandServer::ClientBuffer *buffer);
EglStreamBackend *m_backend;
GLuint m_fbo, m_rbo, m_textureId;
GLenum m_format;
};
} // namespace
#endif

View file

@ -16,7 +16,6 @@
#cmakedefine01 HAVE_X11_XINPUT
#cmakedefine01 HAVE_GBM
#cmakedefine01 HAVE_GBM_BO_GET_FD_FOR_PLANE
#cmakedefine01 HAVE_EGL_STREAMS
#cmakedefine01 HAVE_WAYLAND_EGL
#cmakedefine01 HAVE_SYS_PRCTL_H
#cmakedefine01 HAVE_PR_SET_DUMPABLE

View file

@ -1512,12 +1512,6 @@ QString Workspace::supportInformation() const
support.append(yes);
#else
support.append(no);
#endif
support.append(QStringLiteral("HAVE_EGL_STREAMS: "));
#if HAVE_EGL_STREAMS
support.append(yes);
#else
support.append(no);
#endif
support.append(QStringLiteral("HAVE_X11_XCB: "));
#if HAVE_X11_XCB