diff --git a/CMakeLists.txt b/CMakeLists.txt
index d0a0ace190..0e788a88d1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -210,6 +210,11 @@ if(HAVE_DRM AND gbm_FOUND)
set(HAVE_GBM TRUE)
endif()
+option(KWIN_BUILD_EGL_STREAM_BACKEND "Enable building of EGLStream based DRM backend" ON)
+if(HAVE_DRM AND KWIN_BUILD_EGL_STREAM_BACKEND)
+ set(HAVE_EGL_STREAMS TRUE)
+endif()
+
find_package(libhybris)
set_package_properties(libhybris PROPERTIES TYPE OPTIONAL PURPOSE "Required for libhybris backend")
set(HAVE_LIBHYBRIS ${libhybris_FOUND})
diff --git a/config-kwin.h.cmake b/config-kwin.h.cmake
index 8f680800f0..90efb3e58f 100644
--- a/config-kwin.h.cmake
+++ b/config-kwin.h.cmake
@@ -13,6 +13,7 @@
#cmakedefine01 HAVE_X11_XINPUT
#cmakedefine01 HAVE_DRM
#cmakedefine01 HAVE_GBM
+#cmakedefine01 HAVE_EGL_STREAMS
#cmakedefine01 HAVE_LIBHYBRIS
#cmakedefine01 HAVE_WAYLAND_EGL
#cmakedefine01 HAVE_SYS_PRCTL_H
diff --git a/plugins/platforms/drm/CMakeLists.txt b/plugins/platforms/drm/CMakeLists.txt
index 28cd0c71f8..54ff8b60a5 100644
--- a/plugins/platforms/drm/CMakeLists.txt
+++ b/plugins/platforms/drm/CMakeLists.txt
@@ -21,6 +21,12 @@ if(HAVE_GBM)
)
endif()
+if(HAVE_EGL_STREAMS)
+ set(DRM_SOURCES ${DRM_SOURCES}
+ egl_stream_backend.cpp
+ )
+endif()
+
include_directories(${CMAKE_SOURCE_DIR}/platformsupport/scenes/opengl)
add_library(KWinWaylandDrmBackend MODULE ${DRM_SOURCES})
diff --git a/plugins/platforms/drm/drm_backend.cpp b/plugins/platforms/drm/drm_backend.cpp
index 4f07d8c4cb..10f1fef091 100644
--- a/plugins/platforms/drm/drm_backend.cpp
+++ b/plugins/platforms/drm/drm_backend.cpp
@@ -35,6 +35,9 @@ along with this program. If not, see .
#include "egl_gbm_backend.h"
#include
#endif
+#if HAVE_EGL_STREAMS
+#include "egl_stream_backend.h"
+#endif
// KWayland
#include
#include
@@ -74,6 +77,11 @@ DrmBackend::DrmBackend(QObject *parent)
, m_udevMonitor(m_udev->monitor())
, m_dpmsFilter()
{
+#if HAVE_EGL_STREAMS
+ if (qEnvironmentVariableIsSet("KWIN_DRM_USE_EGL_STREAMS")) {
+ m_useEglStreams = true;
+ }
+#endif
setSupportsGammaControl(true);
handleOutputs();
}
@@ -250,9 +258,10 @@ void DrmBackend::openDrm()
qCWarning(KWIN_DRM) << "Did not find a GPU";
return;
}
- int fd = LogindIntegration::self()->takeDevice(device->devNode());
+ m_devNode = device->devNode();
+ int fd = LogindIntegration::self()->takeDevice(m_devNode.constData());
if (fd < 0) {
- qCWarning(KWIN_DRM) << "failed to open drm device at" << device->devNode();
+ qCWarning(KWIN_DRM) << "failed to open drm device at" << m_devNode;
return;
}
m_fd = fd;
@@ -605,13 +614,13 @@ DrmOutput *DrmBackend::findOutput(const QByteArray &uuid)
return nullptr;
}
-void DrmBackend::present(DrmBuffer *buffer, DrmOutput *output)
+bool DrmBackend::present(DrmBuffer *buffer, DrmOutput *output)
{
if (!buffer || buffer->bufferId() == 0) {
if (m_deleteBufferAfterPageFlip) {
delete buffer;
}
- return;
+ return false;
}
if (output->present(buffer)) {
@@ -619,13 +628,24 @@ void DrmBackend::present(DrmBuffer *buffer, DrmOutput *output)
if (m_pageFlipsPending == 1 && Compositor::self()) {
Compositor::self()->aboutToSwapBuffers();
}
+ return true;
} else if (m_deleteBufferAfterPageFlip) {
delete buffer;
}
+ return false;
}
void DrmBackend::initCursor()
{
+
+#if HAVE_EGL_STREAMS
+ // Hardware cursors aren't currently supported with EGLStream backend,
+ // possibly an NVIDIA driver bug
+ if (m_useEglStreams) {
+ setSoftWareCursor(true);
+ }
+#endif
+
m_cursorEnabled = waylandServer()->seat()->hasPointer();
connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::hasPointerChanged, this,
[this] {
@@ -733,6 +753,13 @@ QPainterBackend *DrmBackend::createQPainterBackend()
OpenGLBackend *DrmBackend::createOpenGLBackend()
{
+#if HAVE_EGL_STREAMS
+ if (m_useEglStreams) {
+ m_deleteBufferAfterPageFlip = false;
+ return new EglStreamBackend(this);
+ }
+#endif
+
#if HAVE_GBM
m_deleteBufferAfterPageFlip = true;
return new EglGbmBackend(this);
@@ -774,6 +801,10 @@ QVector DrmBackend::supportedCompositors() const
}
#if HAVE_GBM
return QVector{OpenGLCompositing, QPainterCompositing};
+#elif HAVE_EGL_STREAMS
+ return m_useEglStreams ?
+ QVector{OpenGLCompositing, QPainterCompositing} :
+ QVector{QPainterCompositing};
#else
return QVector{QPainterCompositing};
#endif
@@ -787,6 +818,9 @@ QString DrmBackend::supportInformation() const
s << "Name: " << "DRM" << endl;
s << "Active: " << m_active << endl;
s << "Atomic Mode Setting: " << m_atomicModeSetting << endl;
+#if HAVE_EGL_STREAMS
+ s << "Using EGL Streams: " << m_useEglStreams << endl;
+#endif
return supportInfo;
}
diff --git a/plugins/platforms/drm/drm_backend.h b/plugins/platforms/drm/drm_backend.h
index 45e37e11b0..cd90926abf 100644
--- a/plugins/platforms/drm/drm_backend.h
+++ b/plugins/platforms/drm/drm_backend.h
@@ -85,7 +85,7 @@ public:
#if HAVE_GBM
DrmSurfaceBuffer *createBuffer(const std::shared_ptr &surface);
#endif
- void present(DrmBuffer *buffer, DrmOutput *output);
+ bool present(DrmBuffer *buffer, DrmOutput *output);
int fd() const {
return m_fd;
@@ -125,6 +125,16 @@ public:
return m_gbmDevice;
}
+ QByteArray devNode() const {
+ return m_devNode;
+ }
+
+#if HAVE_EGL_STREAMS
+ bool useEglStreams() const {
+ return m_useEglStreams;
+ }
+#endif
+
QVector supportedCompositors() const override;
QString supportInformation() const override;
@@ -182,6 +192,10 @@ private:
QSize m_cursorSize;
int m_pageFlipsPending = 0;
bool m_active = false;
+ QByteArray m_devNode;
+#if HAVE_EGL_STREAMS
+ bool m_useEglStreams = false;
+#endif
// all available planes: primarys, cursors and overlays
QVector m_planes;
QVector m_overlayPlanes;
diff --git a/plugins/platforms/drm/drm_output.cpp b/plugins/platforms/drm/drm_output.cpp
index 5c049ce5d5..8432f3c1c1 100644
--- a/plugins/platforms/drm/drm_output.cpp
+++ b/plugins/platforms/drm/drm_output.cpp
@@ -909,6 +909,15 @@ bool DrmOutput::presentAtomically(DrmBuffer *buffer)
return false;
}
+#if HAVE_EGL_STREAMS
+ if (m_backend->useEglStreams() && !m_modesetRequested) {
+ // EglStreamBackend queues normal page flips through EGL,
+ // modesets are still performed through DRM-KMS
+ m_pageFlipPending = true;
+ return true;
+ }
+#endif
+
m_primaryPlane->setNext(buffer);
m_nextPlanesFlipList << m_primaryPlane;
@@ -1052,7 +1061,13 @@ bool DrmOutput::doAtomicCommit(AtomicCommitMode mode)
// TODO: Evaluating this condition should only be necessary, as long as we expect older kernels than 4.10.
flags |= DRM_MODE_ATOMIC_NONBLOCK;
}
- flags |= DRM_MODE_PAGE_FLIP_EVENT;
+
+#if HAVE_EGL_STREAMS
+ if (!m_backend->useEglStreams())
+ // EglStreamBackend uses the NV_output_drm_flip_event EGL extension
+ // to register the flip event through eglStreamConsumerAcquireAttribNV
+#endif
+ flags |= DRM_MODE_PAGE_FLIP_EVENT;
}
} else {
flags |= DRM_MODE_ATOMIC_TEST_ONLY;
diff --git a/plugins/platforms/drm/drm_output.h b/plugins/platforms/drm/drm_output.h
index de3e75699f..0c8ea71b44 100644
--- a/plugins/platforms/drm/drm_output.h
+++ b/plugins/platforms/drm/drm_output.h
@@ -83,6 +83,13 @@ public:
return m_uuid;
}
+ const DrmCrtc *crtc() const {
+ return m_crtc;
+ }
+ const DrmPlane *primaryPlane() const {
+ return m_primaryPlane;
+ }
+
bool initCursor(const QSize &cursorSize);
bool supportsTransformations() const;
diff --git a/plugins/platforms/drm/egl_stream_backend.cpp b/plugins/platforms/drm/egl_stream_backend.cpp
new file mode 100644
index 0000000000..374ab42bbb
--- /dev/null
+++ b/plugins/platforms/drm/egl_stream_backend.cpp
@@ -0,0 +1,692 @@
+/********************************************************************
+ KWin - the KDE window manager
+ This file is part of the KDE project.
+
+Copyright (C) 2019 NVIDIA Inc.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*********************************************************************/
+#include "egl_stream_backend.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 "logind.h"
+#include "options.h"
+#include "scene.h"
+#include "screens.h"
+#include "wayland_server.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+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 *b)
+ : AbstractEglBackend(), m_backend(b)
+{
+ setIsDirectRendering(true);
+ setSyncsToVBlank(true);
+ connect(m_backend, &DrmBackend::outputAdded, this, &EglStreamBackend::createOutput);
+ connect(m_backend, &DrmBackend::outputRemoved, this,
+ [this] (DrmOutput *output) {
+ auto it = std::find_if(m_outputs.begin(), m_outputs.end(),
+ [output] (const Output &o) {
+ return o.output == output;
+ });
+ if (it == m_outputs.end()) {
+ return;
+ }
+ cleanupOutput(*it);
+ m_outputs.erase(it);
+ });
+}
+
+EglStreamBackend::~EglStreamBackend()
+{
+ cleanup();
+}
+
+void EglStreamBackend::cleanupSurfaces()
+{
+ for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) {
+ cleanupOutput(*it);
+ }
+ m_outputs.clear();
+}
+
+void EglStreamBackend::cleanupOutput(const Output &o)
+{
+ if (o.buffer != nullptr) {
+ delete o.buffer;
+ }
+ if (o.eglSurface != EGL_NO_SURFACE) {
+ eglDestroySurface(eglDisplay(), o.eglSurface);
+ }
+ if (o.eglStream != EGL_NO_STREAM_KHR) {
+ pEglDestroyStreamKHR(eglDisplay(), o.eglStream);
+ }
+}
+
+bool EglStreamBackend::initializeEgl()
+{
+ initClientExtensions();
+ EGLDisplay display = m_backend->sceneEglDisplay();
+ 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 devices(numDevices);
+ eglQueryDevicesEXT(numDevices, devices.data(), &numDevices);
+ for (EGLDeviceEXT device : devices) {
+ const char *drmDeviceFile = eglQueryDeviceStringEXT(device, EGL_DRM_DEVICE_FILE_EXT);
+ if (m_backend->devNode() != 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_backend->fd(),
+ EGL_NONE
+ };
+ display = eglGetPlatformDisplayEXT(EGL_PLATFORM_DEVICE_EXT, device, platformAttribs);
+ break;
+ }
+ }
+
+ if (display == EGL_NO_DISPLAY) {
+ setFailed("No suitable EGL device found");
+ return false;
+ }
+
+ setEglDisplay(display);
+ if (!initEglAPI()) {
+ return false;
+ }
+
+ const QVector 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(KWayland::Server::SurfaceInterface *surface)
+{
+ auto it = m_streamTextures.find(surface);
+ return it != m_streamTextures.end() ?
+ &it.value() :
+ nullptr;
+}
+
+void EglStreamBackend::attachStreamConsumer(KWayland::Server::SurfaceInterface *surface,
+ void *eglStream,
+ wl_array *attribs)
+{
+ QVector 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";
+ 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, &KWayland::Server::Resource::unbound, this,
+ [surface, this]() {
+ const StreamTexture &st = m_streamTextures.take(surface);
+ pEglDestroyStreamKHR(eglDisplay(), st.stream);
+ glDeleteTextures(1, &st.texture);
+ });
+ }
+
+ glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture);
+ if (!pEglStreamConsumerGLTextureExternalKHR(eglDisplay(), stream)) {
+ qCWarning(KWIN_DRM) << "Failed to bind EGL stream to texture";
+ }
+ glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0);
+}
+
+void EglStreamBackend::init()
+{
+ if (!m_backend->atomicModeSetting()) {
+ setFailed("EGLStream backend requires atomic modesetting");
+ return;
+ }
+
+ if (!initializeEgl()) {
+ setFailed("Failed to initialize EGL api");
+ return;
+ }
+ if (!initRenderingContext()) {
+ setFailed("Failed to initialize rendering context");
+ return;
+ }
+
+ initKWinGL();
+ setSupportsBufferAge(false);
+ initWayland();
+
+ using namespace KWayland::Server;
+ m_eglStreamControllerInterface = waylandServer()->display()->createEglStreamControllerInterface();
+ connect(m_eglStreamControllerInterface, &EglStreamControllerInterface::streamConsumerAttached, this,
+ &EglStreamBackend::attachStreamConsumer);
+ m_eglStreamControllerInterface->create();
+ if (!m_eglStreamControllerInterface->isValid()) {
+ setFailed("failed to initialize wayland-eglstream-controller interface");
+ }
+}
+
+bool EglStreamBackend::initRenderingContext()
+{
+ initBufferConfigs();
+
+ if (!createContext()) {
+ return false;
+ }
+
+ const auto outputs = m_backend->drmOutputs();
+ for (DrmOutput *drmOutput : outputs) {
+ createOutput(drmOutput);
+ }
+ if (m_outputs.isEmpty()) {
+ qCCritical(KWIN_DRM) << "Failed to create output surface";
+ return false;
+ }
+ // set our first surface as the one for the abstract backend
+ setSurface(m_outputs.first().eglSurface);
+
+ return makeContextCurrent(m_outputs.first());
+}
+
+bool EglStreamBackend::resetOutput(Output &o, DrmOutput *drmOutput)
+{
+ o.output = drmOutput;
+ if (o.buffer != nullptr) {
+ delete o.buffer;
+ }
+ // dumb buffer used for modesetting
+ o.buffer = m_backend->createBuffer(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);
+ }
+ eglDestroySurface(eglDisplay(), o.eglSurface);
+ }
+
+ if (o.eglStream != EGL_NO_STREAM_KHR) {
+ pEglDestroyStreamKHR(eglDisplay(), o.eglStream);
+ }
+
+ o.eglStream = stream;
+ o.eglSurface = eglSurface;
+ return true;
+}
+
+void EglStreamBackend::createOutput(DrmOutput *drmOutput)
+{
+ Output o;
+ if (!resetOutput(o, drmOutput)) {
+ return;
+ }
+
+ connect(drmOutput, &DrmOutput::modeChanged, this,
+ [drmOutput, this] {
+ auto it = std::find_if(m_outputs.begin(), m_outputs.end(),
+ [drmOutput] (const auto &o) {
+ return o.output == drmOutput;
+ }
+ );
+ if (it == m_outputs.end()) {
+ return;
+ }
+ resetOutput(*it, drmOutput);
+ }
+ );
+ m_outputs << o;
+}
+
+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";
+ return false;
+ }
+
+ EGLint error = eglGetError();
+ if (error != EGL_SUCCESS) {
+ qCWarning(KWIN_DRM) << "Error occurred while making EGL context current" << error;
+ return false;
+ }
+
+ const QSize &overall = screens()->size();
+ const QRect &v = output.output->geometry();
+ qreal scale = output.output->scale();
+ glViewport(-v.x() * scale, (v.height() - overall.height() + v.y()) * scale,
+ overall.width() * scale, overall.height() * scale);
+ 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";
+ return false;
+ }
+ if (count == 0) {
+ qCCritical(KWIN_DRM) << "No suitable EGL config found";
+ return false;
+ }
+
+ setConfig(config);
+ return true;
+}
+
+void EglStreamBackend::present()
+{
+ for (auto &o : m_outputs) {
+ makeContextCurrent(o);
+ presentOnOutput(o);
+ }
+}
+
+void EglStreamBackend::presentOnOutput(EglStreamBackend::Output &o)
+{
+ eglSwapBuffers(eglDisplay(), o.eglSurface);
+ if (!m_backend->present(o.buffer, o.output)) {
+ return;
+ }
+
+ EGLAttrib acquireAttribs[] = {
+ EGL_DRM_FLIP_EVENT_DATA_NV, (EGLAttrib)o.output,
+ EGL_NONE,
+ };
+ if (!pEglStreamConsumerAcquireAttribNV(eglDisplay(), o.eglStream, acquireAttribs)) {
+ qCWarning(KWIN_DRM) << "Failed to acquire output EGL stream frame";
+ }
+}
+
+void EglStreamBackend::screenGeometryChanged(const QSize &size)
+{
+ Q_UNUSED(size)
+}
+
+SceneOpenGLTexturePrivate *EglStreamBackend::createBackendTexture(SceneOpenGLTexture *texture)
+{
+ return new EglStreamTexture(texture, this);
+}
+
+QRegion EglStreamBackend::prepareRenderingFrame()
+{
+ startRenderTimer();
+ return QRegion();
+}
+
+QRegion EglStreamBackend::prepareRenderingForScreen(int screenId)
+{
+ const Output &o = m_outputs.at(screenId);
+ makeContextCurrent(o);
+ return o.output->geometry();
+}
+
+void EglStreamBackend::endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion)
+{
+ Q_UNUSED(renderedRegion)
+ Q_UNUSED(damagedRegion)
+}
+
+void EglStreamBackend::endRenderingFrameForScreen(int screenId, const QRegion &renderedRegion, const QRegion &damagedRegion)
+{
+ Q_UNUSED(renderedRegion);
+ Q_UNUSED(damagedRegion);
+ Output &o = m_outputs[screenId];
+ presentOnOutput(o);
+}
+
+bool EglStreamBackend::usesOverlayWindow() const
+{
+ return false;
+}
+
+bool EglStreamBackend::perScreenRendering() const
+{
+ return true;
+}
+
+/************************************************
+ * EglTexture
+ ************************************************/
+
+EglStreamTexture::EglStreamTexture(SceneOpenGLTexture *texture, EglStreamBackend *backend)
+ : AbstractEglTexture(texture, backend), m_backend(backend), m_fbo(0), m_rbo(0)
+{
+}
+
+EglStreamTexture::~EglStreamTexture()
+{
+ glDeleteRenderbuffers(1, &m_rbo);
+ glDeleteFramebuffers(1, &m_fbo);
+}
+
+bool EglStreamTexture::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";
+ 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";
+ }
+ }
+
+ // Re-use previous texture contents if no new frame is available
+ // or if acquisition fails for some reason
+ return false;
+}
+
+void EglStreamTexture::createFbo()
+{
+ glDeleteRenderbuffers(1, &m_rbo);
+ glDeleteFramebuffers(1, &m_fbo);
+
+ glGenFramebuffers(1, &m_fbo);
+ glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
+ glGenRenderbuffers(1, &m_rbo);
+ glBindRenderbuffer(GL_RENDERBUFFER, m_rbo);
+ glRenderbufferStorage(GL_RENDERBUFFER, m_format, m_size.width(), m_size.height());
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_rbo);
+ glBindRenderbuffer(GL_RENDERBUFFER, 0);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+}
+
+// Renders the contents of the given EXTERNAL_OES texture
+// to the scratch framebuffer, then copies this to m_texture
+void EglStreamTexture::copyExternalTexture(GLuint tex)
+{
+ GLint oldViewport[4], oldProgram;
+ glGetIntegerv(GL_VIEWPORT, oldViewport);
+ glViewport(0, 0, m_size.width(), m_size.height());
+ glGetIntegerv(GL_CURRENT_PROGRAM, &oldProgram);
+ 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_target, 0, m_format, 0, 0, m_size.width(), m_size.height(), 0);
+ texture()->unbind();
+
+ glDisable(GL_TEXTURE_EXTERNAL_OES);
+ glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0);
+ glBindRenderbuffer(GL_RENDERBUFFER, 0);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ glUseProgram(oldProgram);
+ glViewport(oldViewport[0], oldViewport[1], oldViewport[2], oldViewport[3]);
+}
+
+bool EglStreamTexture::attachBuffer(KWayland::Server::BufferInterface *buffer)
+{
+ QSize oldSize = m_size;
+ m_size = buffer->size();
+ 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);
+ updateMatrix();
+
+ return oldSize != m_size ||
+ oldFormat != m_format ||
+ wasYInverted != texture()->isYInverted();
+}
+
+bool EglStreamTexture::loadTexture(WindowPixmap *pixmap)
+{
+ using namespace KWayland::Server;
+ SurfaceInterface *surface = pixmap->surface();
+ const EglStreamBackend::StreamTexture *st = m_backend->lookupStreamTexture(surface);
+ if (!pixmap->buffer().isNull() && st != nullptr) {
+
+ glGenTextures(1, &m_texture);
+ texture()->setWrapMode(GL_CLAMP_TO_EDGE);
+ texture()->setFilter(GL_LINEAR);
+
+ attachBuffer(surface->buffer());
+ createFbo();
+ surface->resetTrackedDamage();
+
+ if (acquireStreamFrame(st->stream)) {
+ copyExternalTexture(st->texture);
+ if (!pEglStreamConsumerReleaseKHR(m_backend->eglDisplay(), st->stream)) {
+ qCWarning(KWIN_DRM) << "Failed to release EGL stream";
+ }
+ }
+ return true;
+ } else {
+ // Not an EGLStream surface
+ return AbstractEglTexture::loadTexture(pixmap);
+ }
+}
+
+void EglStreamTexture::updateTexture(WindowPixmap *pixmap)
+{
+ using namespace KWayland::Server;
+ SurfaceInterface *surface = pixmap->surface();
+ const EglStreamBackend::StreamTexture *st = m_backend->lookupStreamTexture(surface);
+ if (!pixmap->buffer().isNull() && st != nullptr) {
+
+ if (attachBuffer(surface->buffer())) {
+ createFbo();
+ }
+ surface->resetTrackedDamage();
+
+ if (acquireStreamFrame(st->stream)) {
+ copyExternalTexture(st->texture);
+ if (!pEglStreamConsumerReleaseKHR(m_backend->eglDisplay(), st->stream)) {
+ qCWarning(KWIN_DRM) << "Failed to release EGL stream";
+ }
+ }
+ } else {
+ // Not an EGLStream surface
+ AbstractEglTexture::updateTexture(pixmap);
+ }
+}
+
+} // namespace
diff --git a/plugins/platforms/drm/egl_stream_backend.h b/plugins/platforms/drm/egl_stream_backend.h
new file mode 100644
index 0000000000..6bae8e76c4
--- /dev/null
+++ b/plugins/platforms/drm/egl_stream_backend.h
@@ -0,0 +1,116 @@
+/********************************************************************
+ KWin - the KDE window manager
+ This file is part of the KDE project.
+
+Copyright (C) 2019 NVIDIA Inc.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*********************************************************************/
+#ifndef KWIN_EGL_STREAM_BACKEND_H
+#define KWIN_EGL_STREAM_BACKEND_H
+#include "abstract_egl_backend.h"
+#include
+#include
+#include
+
+namespace KWin
+{
+
+class DrmBackend;
+class DrmOutput;
+class DrmBuffer;
+class ShellClient;
+
+/**
+ * @brief OpenGL Backend using Egl with an EGLDevice.
+ **/
+class EglStreamBackend : public AbstractEglBackend
+{
+ Q_OBJECT
+public:
+ EglStreamBackend(DrmBackend *b);
+ virtual ~EglStreamBackend();
+ void screenGeometryChanged(const QSize &size) override;
+ SceneOpenGLTexturePrivate *createBackendTexture(SceneOpenGLTexture *texture) override;
+ QRegion prepareRenderingFrame() override;
+ void endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) override;
+ void endRenderingFrameForScreen(int screenId, const QRegion &damage, const QRegion &damagedRegion) override;
+ bool usesOverlayWindow() const override;
+ bool perScreenRendering() const override;
+ QRegion prepareRenderingForScreen(int screenId) override;
+ void init() override;
+
+protected:
+ void present() override;
+ void cleanupSurfaces() override;
+
+private:
+ bool initializeEgl();
+ bool initBufferConfigs();
+ bool initRenderingContext();
+ struct StreamTexture
+ {
+ EGLStreamKHR stream;
+ GLuint texture;
+ };
+ StreamTexture *lookupStreamTexture(KWayland::Server::SurfaceInterface *surface);
+ void attachStreamConsumer(KWayland::Server::SurfaceInterface *surface,
+ void *eglStream,
+ wl_array *attribs);
+ struct Output
+ {
+ DrmOutput *output = nullptr;
+ DrmBuffer *buffer = nullptr;
+ EGLSurface eglSurface = EGL_NO_SURFACE;
+ EGLStreamKHR eglStream = EGL_NO_STREAM_KHR;
+ };
+ bool resetOutput(Output &output, DrmOutput *drmOutput);
+ bool makeContextCurrent(const Output &output);
+ void presentOnOutput(Output &output);
+ void cleanupOutput(const Output &output);
+ void createOutput(DrmOutput *output);
+
+ DrmBackend *m_backend;
+ QVector