diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -210,6 +210,11 @@ 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 --- 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 --- a/plugins/platforms/drm/CMakeLists.txt +++ b/plugins/platforms/drm/CMakeLists.txt @@ -21,6 +21,12 @@ ) 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.h b/plugins/platforms/drm/drm_backend.h --- a/plugins/platforms/drm/drm_backend.h +++ b/plugins/platforms/drm/drm_backend.h @@ -85,7 +85,7 @@ #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 @@ 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 @@ 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_backend.cpp b/plugins/platforms/drm/drm_backend.cpp --- a/plugins/platforms/drm/drm_backend.cpp +++ b/plugins/platforms/drm/drm_backend.cpp @@ -35,6 +35,9 @@ #include "egl_gbm_backend.h" #include #endif +#if HAVE_EGL_STREAMS +#include "egl_stream_backend.h" +#endif // KWayland #include #include @@ -74,6 +77,11 @@ , 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 @@ 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,27 +614,38 @@ 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)) { m_pageFlipsPending++; 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 @@ 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 @@ } #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 @@ 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_output.h b/plugins/platforms/drm/drm_output.h --- a/plugins/platforms/drm/drm_output.h +++ b/plugins/platforms/drm/drm_output.h @@ -83,6 +83,13 @@ 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/drm_output.cpp b/plugins/platforms/drm/drm_output.cpp --- a/plugins/platforms/drm/drm_output.cpp +++ b/plugins/platforms/drm/drm_output.cpp @@ -909,6 +909,15 @@ 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 @@ // 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/egl_stream_backend.h b/plugins/platforms/drm/egl_stream_backend.h new file mode 100644 --- /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 m_outputs; + KWayland::Server::EglStreamControllerInterface *m_eglStreamControllerInterface; + QHash m_streamTextures; + + friend class EglStreamTexture; +}; + +/** + * @brief External texture bound to an EGLStreamKHR. + **/ +class EglStreamTexture : public AbstractEglTexture +{ +public: + virtual ~EglStreamTexture(); + bool loadTexture(WindowPixmap *pixmap) override; + void updateTexture(WindowPixmap *pixmap) override; + +private: + EglStreamTexture(SceneOpenGLTexture *texture, EglStreamBackend *backend); + bool acquireStreamFrame(EGLStreamKHR stream); + void createFbo(); + void copyExternalTexture(GLuint tex); + bool attachBuffer(KWayland::Server::BufferInterface *buffer); + EglStreamBackend *m_backend; + GLuint m_fbo, m_rbo; + GLenum m_format; + friend class EglStreamBackend; +}; + +} // namespace + +#endif diff --git a/plugins/platforms/drm/egl_stream_backend.cpp b/plugins/platforms/drm/egl_stream_backend.cpp new file mode 100644 --- /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/workspace.cpp b/workspace.cpp --- a/workspace.cpp +++ b/workspace.cpp @@ -1399,6 +1399,12 @@ 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