diff --git a/platformsupport/scenes/opengl/abstract_egl_backend.h b/platformsupport/scenes/opengl/abstract_egl_backend.h --- a/platformsupport/scenes/opengl/abstract_egl_backend.h +++ b/platformsupport/scenes/opengl/abstract_egl_backend.h @@ -25,6 +25,7 @@ #include #include #include +#include class QOpenGLFramebufferObject; @@ -76,6 +77,7 @@ bool isOpenGLES() const; bool createContext(); + GLboolean queryWaylandBufferWL(wl_resource *resource, EGLint attribute, EGLint *value); private: void unbindWaylandDisplay(); diff --git a/platformsupport/scenes/opengl/abstract_egl_backend.cpp b/platformsupport/scenes/opengl/abstract_egl_backend.cpp --- a/platformsupport/scenes/opengl/abstract_egl_backend.cpp +++ b/platformsupport/scenes/opengl/abstract_egl_backend.cpp @@ -127,6 +127,13 @@ return eglGetProcAddress(name); } +GLboolean AbstractEglBackend::queryWaylandBufferWL(wl_resource *buffer, + EGLint attribute, EGLint *value) +{ + return eglQueryWaylandBufferWL && + eglQueryWaylandBufferWL(eglDisplay(), buffer, attribute, value); +} + void AbstractEglBackend::initKWinGL() { GLPlatform *glPlatform = GLPlatform::instance(); 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 @@ -10,6 +10,7 @@ logging.cpp scene_qpainter_drm_backend.cpp screens_drm.cpp + egl_stream_backend.cpp ) if(HAVE_GBM) @@ -25,7 +26,7 @@ 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 SceneQPainterBackend SceneOpenGLBackend) +target_link_libraries(KWinWaylandDrmBackend kwin Libdrm::Libdrm SceneQPainterBackend SceneOpenGLBackend dl wayland-server) if(HAVE_GBM) target_link_libraries(KWinWaylandDrmBackend gbm::gbm) 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 @@ -125,6 +125,12 @@ return m_gbmDevice; } + QByteArray devNode() const { + return m_devNode; + } + + void queuePageFlip(); + QVector supportedCompositors() const override; QString supportInformation() const override; @@ -182,6 +188,8 @@ QSize m_cursorSize; int m_pageFlipsPending = 0; bool m_active = false; + QByteArray m_devNode; + bool m_useEglDevice = false; // 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,7 @@ #include "egl_gbm_backend.h" #include #endif +#include "egl_stream_backend.h" // KWayland #include #include @@ -74,6 +75,9 @@ , m_udevMonitor(m_udev->monitor()) , m_dpmsFilter() { + if (qEnvironmentVariableIsSet("KWIN_DRM_USE_EGLDEVICE")) { + m_useEglDevice = true; + } setSupportsGammaControl(true); handleOutputs(); } @@ -250,9 +254,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; @@ -273,7 +278,8 @@ m_drmId = device->sysNum(); // trying to activate Atomic Mode Setting (this means also Universal Planes) - if (!qEnvironmentVariableIsSet("KWIN_DRM_NO_AMS")) { + // note: Atomic Mode Setting is not currently supported with EGLStream backend + if (!qEnvironmentVariableIsSet("KWIN_DRM_NO_AMS") && !m_useEglDevice) { if (drmSetClientCap(m_fd, DRM_CLIENT_CAP_ATOMIC, 1) == 0) { qCDebug(KWIN_DRM) << "Using Atomic Mode Setting."; m_atomicModeSetting = true; @@ -605,6 +611,13 @@ return nullptr; } +void DrmBackend::queuePageFlip() { + ++m_pageFlipsPending; + if (m_pageFlipsPending == 1 && Compositor::self()) { + Compositor::self()->aboutToSwapBuffers(); + } +} + void DrmBackend::present(DrmBuffer *buffer, DrmOutput *output) { if (!buffer || buffer->bufferId() == 0) { @@ -615,10 +628,7 @@ } if (output->present(buffer)) { - m_pageFlipsPending++; - if (m_pageFlipsPending == 1 && Compositor::self()) { - Compositor::self()->aboutToSwapBuffers(); - } + queuePageFlip(); } else if (m_deleteBufferAfterPageFlip) { delete buffer; } @@ -626,6 +636,11 @@ void DrmBackend::initCursor() { + // Hardware cursors aren't currently supported with EGLStream backend + if (m_useEglDevice) { + setSoftWareCursor(true); + } + m_cursorEnabled = waylandServer()->seat()->hasPointer(); connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::hasPointerChanged, this, [this] { @@ -733,12 +748,17 @@ OpenGLBackend *DrmBackend::createOpenGLBackend() { + if (m_useEglDevice) { + m_deleteBufferAfterPageFlip = false; + return new EglStreamBackend(this); + } else { #if HAVE_GBM - m_deleteBufferAfterPageFlip = true; - return new EglGbmBackend(this); + m_deleteBufferAfterPageFlip = true; + return new EglGbmBackend(this); #else - return Platform::createOpenGLBackend(); + return Platform::createOpenGLBackend(); #endif + } } DrmDumbBuffer *DrmBackend::createBuffer(const QSize &size) @@ -784,6 +804,7 @@ s << "Name: " << "DRM" << endl; s << "Active: " << m_active << endl; s << "Atomic Mode Setting: " << m_atomicModeSetting << endl; + s << "Using EGL Device: " << m_useEglDevice << endl; 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 @@ -24,6 +24,7 @@ #include "drm_pointer.h" #include "drm_object.h" #include "drm_object_plane.h" +#include "drm_object_crtc.h" #include #include @@ -83,6 +84,20 @@ return m_uuid; } + const DrmCrtc *crtc() const { + return m_crtc; + } + const DrmPlane *primaryPlane() const { + return m_primaryPlane; + } + + void setPageFlipPending() { + m_pageFlipPending = true; + } + bool pageFlipPending() const { + return m_pageFlipPending; + } + bool initCursor(const QSize &cursorSize); bool supportsTransformations() const; 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,99 @@ +/******************************************************************** + 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 + +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 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; + 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); + 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,705 @@ +/******************************************************************** + 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 "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); +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; + +#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.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"); + return true; +} + +struct StreamTexture { + EGLStreamKHR stream; + GLuint textures[2]; + bool swap; +}; +QHash surfaceTextures; + +static void attachEglStreamConsumer(wl_client *client, + wl_resource *resource, + wl_resource *wl_surface, + wl_resource *wl_eglstream) +{ + Q_UNUSED(client); + Q_UNUSED(resource); + + EGLDisplay eglDisplay = waylandServer()->display()->eglDisplay(); + EGLAttrib streamAttribs[] = { + EGL_WAYLAND_EGLSTREAM_WL, (EGLAttrib)wl_eglstream, + EGL_NONE + }; + EGLStreamKHR stream = pEglCreateStreamAttribNV(eglDisplay, streamAttribs); + if (stream == EGL_NO_STREAM_KHR) { + qCWarning(KWIN_DRM) << "Failed to create EGL stream"; + return; + } + + GLuint texture; + auto it = surfaceTextures.find(wl_surface); + using namespace KWayland::Server; + SurfaceInterface *surface = SurfaceInterface::get(wl_surface); + if (it != surfaceTextures.end()) { + StreamTexture &st = it.value(); + pEglDestroyStreamKHR(eglDisplay, st.stream); + st.stream = stream; + st.swap = true; + texture = st.textures[1]; + } else { + StreamTexture st = { stream, { 0, 0 }, false }; + glGenTextures(2, st.textures); + surfaceTextures.insert(wl_surface, st); + texture = st.textures[0]; + + QObject::connect(surface, &Resource::unbound, + [wl_surface, eglDisplay]() { + const StreamTexture &st = surfaceTextures.take(wl_surface); + pEglDestroyStreamKHR(eglDisplay, st.stream); + glDeleteTextures(2, st.textures); + }); + } + + 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); +} + +const struct { + void (*f)(wl_client *, wl_resource *, wl_resource *, wl_resource *); +} eglStreamControllerImplementation = { + attachEglStreamConsumer +}; +const wl_interface *eglStreamControllerInterface; + +static void bindEglStreamController(wl_client *client, + void *data, uint32_t version, uint32_t id) +{ + wl_resource *resource = wl_resource_create(client, eglStreamControllerInterface, + version, id); + if (!resource) { + wl_client_post_no_memory(client); + } + wl_resource_set_implementation(resource, (void *)&eglStreamControllerImplementation, + data, NULL); +} + +void EglStreamBackend::init() +{ + if (!initializeEgl()) { + setFailed("Failed to initialize EGL api"); + return; + } + if (!initRenderingContext()) { + setFailed("Failed to initialize rendering context"); + return; + } + + initKWinGL(); + setSupportsBufferAge(false); + initWayland(); + + // libnvidia-egl-wayland.so.1 may not be present on all systems, + // so we load it dynamically + void *lib = dlopen("libnvidia-egl-wayland.so.1", RTLD_NOW | RTLD_LAZY); + if (!lib) { + goto fail; + } + eglStreamControllerInterface = (wl_interface *)dlsym(lib, "wl_eglstream_controller_interface"); + if (!eglStreamControllerInterface) { + goto fail; + } + if (wl_global_create(*waylandServer()->display(), + eglStreamControllerInterface, + 1, NULL, bindEglStreamController)) { + return; + } + +fail: + if (lib) { + dlclose(lib); + } + setFailed("Failed to initialize wl_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; + EGLAttrib streamAttribs[] = { + EGL_STREAM_FIFO_LENGTH_KHR, 1, + 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)) { + 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 (LogindIntegration::self()->isActiveSession() && + o.output->isDpmsEnabled() && + !o.output->pageFlipPending()) { + + o.output->setPageFlipPending(); + EGLAttrib acquireAttribs[] = { + EGL_DRM_FLIP_EVENT_DATA_NV, (EGLAttrib)o.output, + EGL_NONE, + }; + pEglStreamConsumerAcquireAttribNV(eglDisplay(), o.eglStream, acquireAttribs); + m_backend->queuePageFlip(); + } + +} + +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::loadTexture(WindowPixmap *pixmap) +{ + using namespace KWayland::Server; + SurfaceInterface *surface = pixmap->surface(); + auto it = surfaceTextures.find(surface->resource()); + if (!pixmap->buffer().isNull() && + it != surfaceTextures.end()) { + + BufferInterface *buffer = surface->buffer(); + m_size = buffer->size(); + m_format = buffer->hasAlphaChannel() ? GL_RGBA : GL_RGB; + createFbo(); + + glGenTextures(1, &m_texture); + texture()->setWrapMode(GL_CLAMP_TO_EDGE); + texture()->setFilter(GL_LINEAR); + EGLint yInverted; + if (!m_backend->queryWaylandBufferWL(buffer->resource(), EGL_WAYLAND_Y_INVERTED_WL, &yInverted)) { + yInverted = EGL_TRUE; + } + texture()->setYInverted(yInverted); + updateMatrix(); + surface->resetTrackedDamage(); + + StreamTexture &st = it.value(); + if (acquireStreamFrame(st.stream)) { + if (st.swap) { + GLuint tmp = st.textures[0]; + st.textures[0] = st.textures[1]; + st.textures[1] = tmp; + st.swap = false; + } + copyExternalTexture(st.textures[0]); + 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(); + auto it = surfaceTextures.find(surface->resource()); + if (!pixmap->buffer().isNull() && + it != surfaceTextures.end()) { + + surface->resetTrackedDamage(); + + StreamTexture &st = it.value(); + if (acquireStreamFrame(st.stream)) { + copyExternalTexture(st.textures[0]); + if (!pEglStreamConsumerReleaseKHR(m_backend->eglDisplay(), st.stream)) { + qCWarning(KWIN_DRM) << "Failed to release EGL stream"; + } + } + } else { + // Not an EGLStream surface + AbstractEglTexture::updateTexture(pixmap); + } +} + +} // namespace