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 @@ -23,6 +23,7 @@ #include "texture.h" #include +#include #include #include @@ -39,6 +40,8 @@ namespace KWin { +class EglDmabufBuffer; + class KWIN_EXPORT AbstractEglBackend : public QObject, public OpenGLBackend { Q_OBJECT @@ -60,6 +63,15 @@ return m_config; } + void removeDmabufBuffer(EglDmabufBuffer *buffer); + + QVector supportedDrmFormats() override; + QVector supportedDrmModifiers(uint32_t format) override; + KWayland::Server::LinuxDmabuf::Buffer *importDmabufBuffer(const QVector &planes, + uint32_t format, + const QSize &size, + KWayland::Server::LinuxDmabuf::Flags flags) override; + protected: AbstractEglBackend(); void setEglDisplay(const EGLDisplay &display); @@ -85,6 +97,8 @@ EGLContext m_context = EGL_NO_CONTEXT; EGLConfig m_config = nullptr; QList m_clientExtensions; + QSet m_dmabufBuffers; + bool m_haveDmabufImport = false; }; class KWIN_EXPORT AbstractEglTexture : public SceneOpenGLTexturePrivate @@ -110,13 +124,41 @@ private: bool loadShmTexture(const QPointer &buffer); bool loadEglTexture(const QPointer &buffer); + bool loadDmabufTexture(const QPointer< KWayland::Server::BufferInterface > &buffer); EGLImageKHR attach(const QPointer &buffer); bool updateFromFBO(const QSharedPointer &fbo); SceneOpenGLTexture *q; AbstractEglBackend *m_backend; EGLImageKHR m_image; }; +class EglDmabufBuffer : public KWayland::Server::LinuxDmabuf::Buffer +{ +public: + using Plane = KWayland::Server::LinuxDmabuf::Plane; + using Flags = KWayland::Server::LinuxDmabuf::Flags; + + EglDmabufBuffer(EGLImage image, + const QVector &planes, + uint32_t format, + const QSize &size, + Flags flags, + AbstractEglBackend *backend); + ~EglDmabufBuffer() override; + + EGLImage image() const { return m_image; } + Flags flags() const { return m_flags; } + const QVector &planes() const { return m_planes; } + + void destroyImage(); + +private: + AbstractEglBackend *m_backend; + EGLImage m_image; + QVector m_planes; + Flags m_flags; +}; + } #endif 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 @@ -37,6 +37,7 @@ #include #include +#include namespace KWin { @@ -48,6 +49,11 @@ eglUnbindWaylandDisplayWL_func eglUnbindWaylandDisplayWL = nullptr; eglQueryWaylandBufferWL_func eglQueryWaylandBufferWL = nullptr; +typedef EGLBoolean (*eglQueryDmaBufFormatsEXT_func) (EGLDisplay dpy, EGLint max_formats, EGLint *formats, EGLint *num_formats); +typedef EGLBoolean (*eglQueryDmaBufModifiersEXT_func) (EGLDisplay dpy, EGLint format, EGLint max_modifiers, EGLuint64KHR *modifiers, EGLBoolean *external_only, EGLint *num_modifiers); +eglQueryDmaBufFormatsEXT_func eglQueryDmaBufFormatsEXT = nullptr; +eglQueryDmaBufModifiersEXT_func eglQueryDmaBufModifiersEXT = nullptr; + #ifndef EGL_WAYLAND_BUFFER_WL #define EGL_WAYLAND_BUFFER_WL 0x31D5 #endif @@ -58,14 +64,58 @@ #define EGL_WAYLAND_Y_INVERTED_WL 0x31DB #endif +#ifndef EGL_EXT_image_dma_buf_import +#define EGL_LINUX_DMA_BUF_EXT 0x3270 +#define EGL_LINUX_DRM_FOURCC_EXT 0x3271 +#define EGL_DMA_BUF_PLANE0_FD_EXT 0x3272 +#define EGL_DMA_BUF_PLANE0_OFFSET_EXT 0x3273 +#define EGL_DMA_BUF_PLANE0_PITCH_EXT 0x3274 +#define EGL_DMA_BUF_PLANE1_FD_EXT 0x3275 +#define EGL_DMA_BUF_PLANE1_OFFSET_EXT 0x3276 +#define EGL_DMA_BUF_PLANE1_PITCH_EXT 0x3277 +#define EGL_DMA_BUF_PLANE2_FD_EXT 0x3278 +#define EGL_DMA_BUF_PLANE2_OFFSET_EXT 0x3279 +#define EGL_DMA_BUF_PLANE2_PITCH_EXT 0x327A +#define EGL_YUV_COLOR_SPACE_HINT_EXT 0x327B +#define EGL_SAMPLE_RANGE_HINT_EXT 0x327C +#define EGL_YUV_CHROMA_HORIZONTAL_SITING_HINT_EXT 0x327D +#define EGL_YUV_CHROMA_VERTICAL_SITING_HINT_EXT 0x327E +#define EGL_ITU_REC601_EXT 0x327F +#define EGL_ITU_REC709_EXT 0x3280 +#define EGL_ITU_REC2020_EXT 0x3281 +#define EGL_YUV_FULL_RANGE_EXT 0x3282 +#define EGL_YUV_NARROW_RANGE_EXT 0x3283 +#define EGL_YUV_CHROMA_SITING_0_EXT 0x3284 +#define EGL_YUV_CHROMA_SITING_0_5_EXT 0x3285 +#endif // EGL_EXT_image_dma_buf_import + +#ifndef EGL_EXT_image_dma_buf_import_modifiers +#define EGL_DMA_BUF_PLANE3_FD_EXT 0x3440 +#define EGL_DMA_BUF_PLANE3_OFFSET_EXT 0x3441 +#define EGL_DMA_BUF_PLANE3_PITCH_EXT 0x3442 +#define EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT 0x3443 +#define EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT 0x3444 +#define EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT 0x3445 +#define EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT 0x3446 +#define EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT 0x3447 +#define EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT 0x3448 +#define EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT 0x3449 +#define EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT 0x344A +#endif // EGL_EXT_image_dma_buf_import_modifiers + AbstractEglBackend::AbstractEglBackend() : QObject(nullptr) , OpenGLBackend() { connect(Compositor::self(), &Compositor::aboutToDestroy, this, &AbstractEglBackend::unbindWaylandDisplay); } -AbstractEglBackend::~AbstractEglBackend() = default; +AbstractEglBackend::~AbstractEglBackend() +{ + for (auto *dmabuf : qAsConst(m_dmabufBuffers)) { + dmabuf->destroyImage(); + } +} void AbstractEglBackend::unbindWaylandDisplay() { @@ -169,6 +219,13 @@ } } } + + if (hasExtension(QByteArrayLiteral("EGL_EXT_image_dma_buf_import_modifiers"))) { + eglQueryDmaBufFormatsEXT = (eglQueryDmaBufFormatsEXT_func) eglGetProcAddress("eglQueryDmaBufFormatsEXT"); + eglQueryDmaBufModifiersEXT = (eglQueryDmaBufModifiersEXT_func) eglGetProcAddress("eglQueryDmaBufModifiersEXT"); + } + + m_haveDmabufImport = hasExtension(QByteArrayLiteral("EGL_EXT_image_dma_buf_import")); } void AbstractEglBackend::initClientExtensions() @@ -318,6 +375,85 @@ kwinApp()->platform()->setSceneEglSurface(surface); } +void AbstractEglBackend::removeDmabufBuffer(EglDmabufBuffer *buffer) +{ + m_dmabufBuffers.remove(buffer); +} + +QVector AbstractEglBackend::supportedDrmFormats() +{ + if (!m_haveDmabufImport || eglQueryDmaBufFormatsEXT == nullptr) + return QVector(); + + EGLint count = 0; + EGLBoolean success = eglQueryDmaBufFormatsEXT(m_display, 0, NULL, &count); + + if (success && count > 0) { + QVector formats(count); + if (eglQueryDmaBufFormatsEXT(m_display, count, (EGLint *) formats.data(), &count)) { + return formats; + } + } + + return QVector(); +} + +QVector AbstractEglBackend::supportedDrmModifiers(uint32_t format) +{ + if (!m_haveDmabufImport || eglQueryDmaBufModifiersEXT == nullptr) + return QVector(); + + EGLint count = 0; + EGLBoolean success = eglQueryDmaBufModifiersEXT(m_display, format, 0, NULL, NULL, &count); + + if (success && count > 0) { + QVector modifiers(count); + if (eglQueryDmaBufModifiersEXT(m_display, format, count, modifiers.data(), NULL, &count)) { + return modifiers; + } + } + + return QVector(); +} + +KWayland::Server::LinuxDmabuf::Buffer *AbstractEglBackend::importDmabufBuffer(const QVector &planes, + uint32_t format, + const QSize &size, + KWayland::Server::LinuxDmabuf::Flags flags) +{ + if (!m_haveDmabufImport) + return nullptr; + + // FIXME: Add support for multi-planar images + if (planes.count() != 1) + return nullptr; + + const EGLint attribs[] = { + EGL_WIDTH, size.width(), + EGL_HEIGHT, size.height(), + EGL_LINUX_DRM_FOURCC_EXT, EGLint(format), + EGL_DMA_BUF_PLANE0_FD_EXT, planes[0].fd, + EGL_DMA_BUF_PLANE0_OFFSET_EXT, EGLint(planes[0].offset), + EGL_DMA_BUF_PLANE0_PITCH_EXT, EGLint(planes[0].stride), + EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, EGLint(planes[0].modifier & 0xffffffff), + EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, EGLint(planes[0].modifier >> 32), + EGL_NONE + }; + + // Note that the EGLImage does NOT take onwership of the file descriptors + EGLImage image = eglCreateImageKHR(m_display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, (EGLClientBuffer) nullptr, attribs); + if (image == EGL_NO_IMAGE_KHR) + return nullptr; + + EglDmabufBuffer *buffer = new EglDmabufBuffer(image, planes, format, size, flags, this); + + // We track imported buffers so we can clean up the EGL images + // from the AbstractEglBackend destructor. + m_dmabufBuffers.insert(buffer); + + return buffer; +} + AbstractEglTexture::AbstractEglTexture(SceneOpenGLTexture *texture, AbstractEglBackend *backend) : SceneOpenGLTexturePrivate() , q(texture) @@ -352,11 +488,12 @@ if (auto s = pixmap->surface()) { s->resetTrackedDamage(); } - if (buffer->shmBuffer()) { + if (buffer->linuxDmabufBuffer()) { + return loadDmabufTexture(buffer); + } else if (buffer->shmBuffer()) { return loadShmTexture(buffer); - } else { - return loadEglTexture(buffer); } + return loadEglTexture(buffer); } void AbstractEglTexture::updateTexture(WindowPixmap *pixmap) @@ -373,12 +510,34 @@ return; } auto s = pixmap->surface(); + if (EglDmabufBuffer *dmabuf = static_cast(buffer->linuxDmabufBuffer())) { + q->bind(); + glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES) dmabuf->image()); + q->unbind(); + if (m_image != EGL_NO_IMAGE_KHR) { + eglDestroyImageKHR(m_backend->eglDisplay(), m_image); + } + m_image = EGL_NO_IMAGE_KHR; // The wl_buffer has ownership of the image + // The origin in a dmabuf-buffer is at the upper-left corner, so the meaning + // of Y-inverted is the inverse of OpenGL. + const bool yInverted = !(dmabuf->flags() & KWayland::Server::LinuxDmabuf::YInverted); + if (m_size != dmabuf->size() || yInverted != q->isYInverted()) { + m_size = dmabuf->size(); + q->setYInverted(yInverted); + } + if (s) { + s->resetTrackedDamage(); + } + return; + } if (!buffer->shmBuffer()) { q->bind(); EGLImageKHR image = attach(buffer); q->unbind(); if (image != EGL_NO_IMAGE_KHR) { - eglDestroyImageKHR(m_backend->eglDisplay(), m_image); + if (m_image != EGL_NO_IMAGE_KHR) { + eglDestroyImageKHR(m_backend->eglDisplay(), m_image); + } m_image = image; } if (s) { @@ -504,6 +663,30 @@ return true; } +bool AbstractEglTexture::loadDmabufTexture(const QPointer< KWayland::Server::BufferInterface > &buffer) +{ + EglDmabufBuffer *dmabuf = static_cast(buffer->linuxDmabufBuffer()); + if (!dmabuf || dmabuf->image() == EGL_NO_IMAGE_KHR) { + qCritical(KWIN_OPENGL) << "Invalid dmabuf-based wl_buffer"; + q->discard(); + return false; + } + + assert(m_image == EGL_NO_IMAGE_KHR); + + glGenTextures(1, &m_texture); + q->setWrapMode(GL_CLAMP_TO_EDGE); + q->setFilter(GL_NEAREST); + q->bind(); + glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES) dmabuf->image()); + q->unbind(); + + m_size = dmabuf->size(); + q->setYInverted(!(dmabuf->flags() & KWayland::Server::LinuxDmabuf::YInverted)); + + return true; +} + EGLImageKHR AbstractEglTexture::attach(const QPointer< KWayland::Server::BufferInterface > &buffer) { EGLint format, yInverted; @@ -546,5 +729,44 @@ return true; } +EglDmabufBuffer::EglDmabufBuffer(EGLImage image, + const QVector &planes, + uint32_t format, + const QSize &size, + Flags flags, + AbstractEglBackend *backend) + : KWayland::Server::LinuxDmabuf::Buffer(format, size), + m_backend(backend), + m_image(image), + m_planes(planes), + m_flags(flags) +{ +} + +EglDmabufBuffer::~EglDmabufBuffer() +{ + if (m_backend) { + m_backend->removeDmabufBuffer(this); + + assert(m_image != EGL_NO_IMAGE_KHR); + eglDestroyImageKHR(m_backend->eglDisplay(), m_image); + } + + // Close all open file descriptors + for (int i = 0; i < m_planes.count(); i++) { + if (m_planes[i].fd != -1) + ::close(m_planes[i].fd); + m_planes[i].fd = -1; + } +} + +void EglDmabufBuffer::destroyImage() +{ + assert(m_image != EGL_NO_IMAGE_KHR); + eglDestroyImageKHR(m_backend->eglDisplay(), m_image); + m_image = EGL_NO_IMAGE_KHR; + m_backend = nullptr; +} + } diff --git a/platformsupport/scenes/opengl/backend.h b/platformsupport/scenes/opengl/backend.h --- a/platformsupport/scenes/opengl/backend.h +++ b/platformsupport/scenes/opengl/backend.h @@ -26,6 +26,8 @@ #include +#include + namespace KWin { class OpenGLBackend; @@ -197,6 +199,30 @@ **/ void copyPixels(const QRegion ®ion); + /** + * Returns the list of the DRM format codes supported by the OpenGL backend. + * + * The default implementation returns an empty vector. + */ + virtual QVector supportedDrmFormats(); + + /** + * Returns the list of the DRM modifiers supported with the given format. + * + * The default implementation returns an empty vector. + */ + virtual QVector supportedDrmModifiers(uint32_t format); + + /** + * Imports a dmabuf-buffer into the OpenGL backend. + * + * The default implementation returns nullptr. + */ + virtual KWayland::Server::LinuxDmabuf::Buffer *importDmabufBuffer(const QVector &planes, + uint32_t format, + const QSize &size, + KWayland::Server::LinuxDmabuf::Flags flags = 0); + protected: /** * @brief Backend specific flushing of frame to screen. diff --git a/platformsupport/scenes/opengl/backend.cpp b/platformsupport/scenes/opengl/backend.cpp --- a/platformsupport/scenes/opengl/backend.cpp +++ b/platformsupport/scenes/opengl/backend.cpp @@ -116,4 +116,29 @@ } } +QVector OpenGLBackend::supportedDrmFormats() +{ + return QVector(); +} + +QVector OpenGLBackend::supportedDrmModifiers(uint32_t format) +{ + Q_UNUSED(format) + + return QVector(); +} + +KWayland::Server::LinuxDmabuf::Buffer *OpenGLBackend::importDmabufBuffer(const QVector &planes, + uint32_t format, + const QSize &size, + KWayland::Server::LinuxDmabuf::Flags flags) +{ + Q_UNUSED(planes) + Q_UNUSED(format) + Q_UNUSED(size) + Q_UNUSED(flags) + + return nullptr; +} + } diff --git a/plugins/scenes/opengl/scene_opengl.h b/plugins/scenes/opengl/scene_opengl.h --- a/plugins/scenes/opengl/scene_opengl.h +++ b/plugins/scenes/opengl/scene_opengl.h @@ -82,6 +82,13 @@ QVector openGLPlatformInterfaceExtensions() const override; + QVector supportedDrmFormats() override final; + QVector supportedDrmModifiers(uint32_t format) override final; + KWayland::Server::LinuxDmabuf::Buffer *importDmabufBuffer(const QVector &planes, + uint32_t format, + const QSize &size, + KWayland::Server::LinuxDmabuf::Flags flags = 0) override final; + static SceneOpenGL *createScene(QObject *parent); protected: diff --git a/plugins/scenes/opengl/scene_opengl.cpp b/plugins/scenes/opengl/scene_opengl.cpp --- a/plugins/scenes/opengl/scene_opengl.cpp +++ b/plugins/scenes/opengl/scene_opengl.cpp @@ -743,6 +743,24 @@ return m_backend->renderTime(); } +QVector SceneOpenGL::supportedDrmFormats() +{ + return m_backend->supportedDrmFormats(); +} + +QVector SceneOpenGL::supportedDrmModifiers(uint32_t format) +{ + return m_backend->supportedDrmModifiers(format); +} + +KWayland::Server::LinuxDmabuf::Buffer *SceneOpenGL::importDmabufBuffer(const QVector &planes, + uint32_t format, + const QSize &size, + KWayland::Server::LinuxDmabuf::Flags flags) +{ + return m_backend->importDmabufBuffer(planes, format, size, flags); +} + QMatrix4x4 SceneOpenGL::transformation(int mask, const ScreenPaintData &data) const { QMatrix4x4 matrix; diff --git a/scene.h b/scene.h --- a/scene.h +++ b/scene.h @@ -25,6 +25,8 @@ #include "utils.h" #include "kwineffects.h" +#include + #include #include @@ -198,6 +200,30 @@ **/ virtual QVector openGLPlatformInterfaceExtensions() const; + /** + * Returns the DRM formats supported by the underlying graphics stack. + * + * The default implementation returns an empty vector. + */ + virtual QVector supportedDrmFormats(); + + /** + * Returns the DRM modifiers supported with the given format. + * + * The default implementation returns an empty vector. + */ + virtual QVector supportedDrmModifiers(uint32_t format); + + /** + * Imports a dmabuf-buffer into the graphics system used by the scene. + * + * The default implementation returns nullptr. + */ + virtual KWayland::Server::LinuxDmabuf::Buffer *importDmabufBuffer(const QVector &planes, + uint32_t format, + const QSize &size, + KWayland::Server::LinuxDmabuf::Flags flags = 0); + Q_SIGNALS: void frameRendered(); void resetCompositing(); diff --git a/scene.cpp b/scene.cpp --- a/scene.cpp +++ b/scene.cpp @@ -679,6 +679,31 @@ return QVector{}; } +QVector Scene::supportedDrmFormats() +{ + return QVector(); +} + +QVector Scene::supportedDrmModifiers(uint32_t format) +{ + Q_UNUSED(format) + + return QVector(); +} + +KWayland::Server::LinuxDmabuf::Buffer *Scene::importDmabufBuffer(const QVector &planes, + uint32_t format, + const QSize &size, + KWayland::Server::LinuxDmabuf::Flags flags) +{ + Q_UNUSED(planes) + Q_UNUSED(format) + Q_UNUSED(size) + Q_UNUSED(flags) + + return nullptr; +} + //**************************************** // Scene::Window //**************************************** diff --git a/wayland_server.h b/wayland_server.h --- a/wayland_server.h +++ b/wayland_server.h @@ -68,14 +68,16 @@ class XdgForeignInterface; class XdgOutputManagerInterface; class KeyStateInterface; +class LinuxDmabufUnstableV1Interface; } } namespace KWin { class ShellClient; class AbstractClient; +class LinuxDmabufBridge; class Toplevel; class KWIN_EXPORT WaylandServer : public QObject @@ -284,6 +286,8 @@ QHash m_clientIds; InitalizationFlags m_initFlags; QVector m_plasmaShellSurfaces; + KWayland::Server::LinuxDmabufUnstableV1Interface *m_linuxDmabuf; + LinuxDmabufBridge *m_linuxDmabufBridge; KWIN_SINGLETON(WaylandServer) }; diff --git a/wayland_server.cpp b/wayland_server.cpp --- a/wayland_server.cpp +++ b/wayland_server.cpp @@ -23,6 +23,7 @@ #include "composite.h" #include "idle_inhibition.h" #include "internal_client.h" +#include "scene.h" #include "screens.h" #include "shell_client.h" #include "workspace.h" @@ -45,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -88,6 +90,32 @@ KWIN_SINGLETON_FACTORY(WaylandServer) +class LinuxDmabufBridge : public LinuxDmabufUnstableV1Interface::Bridge +{ +public: + LinuxDmabufBridge() = default; + ~LinuxDmabufBridge() = default; + + QVector supportedFormats() const override final; + QVector supportedModifiers(uint32_t format) const override final; + LinuxDmabuf::Buffer *importBuffer(const QVector &planes, uint32_t format, const QSize &size, LinuxDmabuf::Flags flags) override final; +}; + +QVector LinuxDmabufBridge::supportedFormats() const +{ + return Compositor::self()->scene()->supportedDrmFormats(); +} + +QVector LinuxDmabufBridge::supportedModifiers(uint32_t format) const +{ + return Compositor::self()->scene()->supportedDrmModifiers(format); +} + +LinuxDmabuf::Buffer *LinuxDmabufBridge::importBuffer(const QVector &planes, uint32_t format, const QSize &size, LinuxDmabuf::Flags flags) +{ + return Compositor::self()->scene()->importDmabufBuffer(planes, format, size, flags); +} + WaylandServer::WaylandServer(QObject *parent) : QObject(parent) { @@ -99,6 +127,8 @@ WaylandServer::~WaylandServer() { destroyInputMethodConnection(); + m_linuxDmabuf->setBridge(nullptr); + delete m_linuxDmabufBridge; } void WaylandServer::destroyInternalConnection() @@ -375,6 +405,12 @@ m_XdgForeign = m_display->createXdgForeignInterface(m_display); m_XdgForeign->create(); + m_linuxDmabufBridge = new LinuxDmabufBridge; + + m_linuxDmabuf = m_display->createLinuxDmabufInterface(m_display); + m_linuxDmabuf->setBridge(m_linuxDmabufBridge); + m_linuxDmabuf->create(); + m_keyState = m_display->createKeyStateInterface(m_display); m_keyState->create();