diff --git a/CMakeLists.txt b/CMakeLists.txt index 3efd773f89..d34bd44740 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -164,6 +164,13 @@ if(Libdrm_FOUND AND UDEV_FOUND) set(HAVE_DRM TRUE) endif() +find_package(gbm) +set_package_properties(gbm PROPERTIES TYPE OPTIONAL PURPOSE "Required for egl ouput of drm backend.") +set(HAVE_GBM FALSE) +if(HAVE_DRM AND gbm_FOUND) + set(HAVE_GBM TRUE) +endif() + find_package(X11) set_package_properties(X11 PROPERTIES DESCRIPTION "X11 libraries" URL "http://www.x.org" @@ -448,6 +455,12 @@ if(HAVE_WAYLAND) screens_drm.cpp ) endif() + if(HAVE_GBM) + set(kwin_KDEINIT_SRCS + ${kwin_KDEINIT_SRCS} + egl_gbm_backend.cpp + ) + endif() endif() if(UDEV_FOUND) @@ -585,6 +598,10 @@ if(HAVE_DRM) set(kwinLibs ${kwinLibs} Libdrm::Libdrm) endif() +if(HAVE_GBM) + set(kwinLibs ${kwinLibs} gbm::gbm) +endif() + add_library(kwin SHARED ${kwin_KDEINIT_SRCS}) set_target_properties(kwin PROPERTIES diff --git a/cmake/modules/Findgbm.cmake b/cmake/modules/Findgbm.cmake new file mode 100644 index 0000000000..6dfc895daa --- /dev/null +++ b/cmake/modules/Findgbm.cmake @@ -0,0 +1,125 @@ +#.rst: +# Findgbm +# ------- +# +# Try to find gbm on a Unix system. +# +# This will define the following variables: +# +# ``gbm_FOUND`` +# True if (the requested version of) gbm is available +# ``gbm_VERSION`` +# The version of gbm +# ``gbm_LIBRARIES`` +# This can be passed to target_link_libraries() instead of the ``gbm::gbm`` +# target +# ``gbm_INCLUDE_DIRS`` +# This should be passed to target_include_directories() if the target is not +# used for linking +# ``gbm_DEFINITIONS`` +# This should be passed to target_compile_options() if the target is not +# used for linking +# +# If ``gbm_FOUND`` is TRUE, it will also define the following imported target: +# +# ``gbm::gbm`` +# The gbm library +# +# In general we recommend using the imported target, as it is easier to use. +# Bear in mind, however, that if the target is in the link interface of an +# exported library, it must be made available by the package config file. + +#============================================================================= +# Copyright 2014 Alex Merry +# Copyright 2014 Martin Gräßlin +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#============================================================================= + +if(CMAKE_VERSION VERSION_LESS 2.8.12) + message(FATAL_ERROR "CMake 2.8.12 is required by Findgbm.cmake") +endif() +if(CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 2.8.12) + message(AUTHOR_WARNING "Your project should require at least CMake 2.8.12 to use Findgbm.cmake") +endif() + +if(NOT WIN32) + # Use pkg-config to get the directories and then use these values + # in the FIND_PATH() and FIND_LIBRARY() calls + find_package(PkgConfig) + pkg_check_modules(PKG_gbm QUIET gbm) + + set(gbm_DEFINITIONS ${PKG_gbm_CFLAGS_OTHER}) + set(gbm_VERSION ${PKG_gbm_VERSION}) + + find_path(gbm_INCLUDE_DIR + NAMES + gbm.h + HINTS + ${PKG_gbm_INCLUDE_DIRS} + ) + find_library(gbm_LIBRARY + NAMES + gbm + HINTS + ${PKG_gbm_LIBRARY_DIRS} + ) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(gbm + FOUND_VAR + gbm_FOUND + REQUIRED_VARS + gbm_LIBRARY + gbm_INCLUDE_DIR + VERSION_VAR + gbm_VERSION + ) + + if(gbm_FOUND AND NOT TARGET gbm::gbm) + add_library(gbm::gbm UNKNOWN IMPORTED) + set_target_properties(gbm::gbm PROPERTIES + IMPORTED_LOCATION "${gbm_LIBRARY}" + INTERFACE_COMPILE_OPTIONS "${gbm_DEFINITIONS}" + INTERFACE_INCLUDE_DIRECTORIES "${gbm_INCLUDE_DIR}" + ) + endif() + + mark_as_advanced(gbm_LIBRARY gbm_INCLUDE_DIR) + + # compatibility variables + set(gbm_LIBRARIES ${gbm_LIBRARY}) + set(gbm_INCLUDE_DIRS ${gbm_INCLUDE_DIR}) + set(gbm_VERSION_STRING ${gbm_VERSION}) + +else() + message(STATUS "Findgbm.cmake cannot find gbm on Windows systems.") + set(gbm_FOUND FALSE) +endif() + +include(FeatureSummary) +set_package_properties(gbm PROPERTIES + URL "http://www.mesa3d.org" + DESCRIPTION "Mesa gbm library." +) diff --git a/config-kwin.h.cmake b/config-kwin.h.cmake index 79052e25fe..699d65cd47 100644 --- a/config-kwin.h.cmake +++ b/config-kwin.h.cmake @@ -16,6 +16,7 @@ #cmakedefine01 HAVE_XCB_SYNC #cmakedefine01 HAVE_X11_XCB #cmakedefine01 HAVE_DRM +#cmakedefine01 HAVE_GBM /* Define to 1 if you have the header file. */ #cmakedefine HAVE_UNISTD_H 1 diff --git a/drm_backend.cpp b/drm_backend.cpp index 9617dec433..928d3bd6db 100644 --- a/drm_backend.cpp +++ b/drm_backend.cpp @@ -25,6 +25,9 @@ along with this program. If not, see . #include "udev.h" #include "utils.h" #include "virtual_terminal.h" +#if HAVE_GBM +#include "egl_gbm_backend.h" +#endif // Qt #include // system @@ -34,6 +37,9 @@ along with this program. If not, see . #include #include #include +#if HAVE_GBM +#include +#endif #include @@ -80,6 +86,12 @@ void DrmBackend::pageFlipHandler(int fd, unsigned int frame, unsigned int sec, u Q_UNUSED(usec) DrmBuffer *buffer = reinterpret_cast(data); buffer->m_backend->m_pageFlipPending = false; +#if HAVE_GBM + if (buffer->m_bo) { + gbm_surface_release_buffer(buffer->m_surface, buffer->m_bo); + buffer->m_bo = nullptr; + } +#endif Compositor::self()->bufferSwapComplete(); } @@ -186,11 +198,26 @@ QPainterBackend *DrmBackend::createQPainterBackend() return new DrmQPainterBackend(this); } +OpenGLBackend *DrmBackend::createOpenGLBackend() +{ +#if HAVE_GBM + return new EglGbmBackend(this); +#endif + return AbstractBackend::createOpenGLBackend(); +} + DrmBuffer *DrmBackend::createBuffer(const QSize &size) { return new DrmBuffer(this, size); } +DrmBuffer *DrmBackend::createBuffer(gbm_surface *surface) +{ +#if HAVE_GBM + return new DrmBuffer(this, surface); +#endif +} + DrmBuffer::DrmBuffer(DrmBackend *backend, const QSize &size) : m_backend(backend) , m_size(size) @@ -210,6 +237,34 @@ DrmBuffer::DrmBuffer(DrmBackend *backend, const QSize &size) m_stride, createArgs.handle, &m_bufferId); } + +#if HAVE_GBM +static void gbmCallback(gbm_bo *bo, void *data) +{ + Q_UNUSED(bo); + delete reinterpret_cast(data); +} +#endif + +DrmBuffer::DrmBuffer(DrmBackend *backend, gbm_surface *surface) + : m_backend(backend) + , m_surface(surface) +{ +#if HAVE_GBM + m_bo = gbm_surface_lock_front_buffer(surface); + if (!m_bo) { + qWarning(KWIN_CORE) << "Locking front buffer failed"; + return; + } + m_size = QSize(gbm_bo_get_width(m_bo), gbm_bo_get_height(m_bo)); + m_stride = gbm_bo_get_stride(m_bo); + if (drmModeAddFB(m_backend->fd(), m_size.width(), m_size.height(), 24, 32, m_stride, gbm_bo_get_handle(m_bo).u32, &m_bufferId) != 0) { + qWarning(KWIN_CORE) << "drmModeAddFB failed"; + } + gbm_bo_set_user_data(m_bo, this, gbmCallback); +#endif +} + DrmBuffer::~DrmBuffer() { delete m_image; @@ -224,6 +279,11 @@ DrmBuffer::~DrmBuffer() destroyArgs.handle = m_handle; drmIoctl(m_backend->fd(), DRM_IOCTL_MODE_DESTROY_DUMB, &destroyArgs); } +#if HAVE_GBM + if (m_bo) { + gbm_surface_release_buffer(m_surface, m_bo); + } +#endif } bool DrmBuffer::map() diff --git a/drm_backend.h b/drm_backend.h index 597ed9b371..92fa769214 100644 --- a/drm_backend.h +++ b/drm_backend.h @@ -25,6 +25,9 @@ along with this program. If not, see . #include #include +struct gbm_bo; +struct gbm_surface; + namespace KWin { @@ -41,9 +44,11 @@ public: Screens *createScreens(QObject *parent = nullptr) override; QPainterBackend *createQPainterBackend() override; + OpenGLBackend* createOpenGLBackend() override; void init(); DrmBuffer *createBuffer(const QSize &size); + DrmBuffer *createBuffer(gbm_surface *surface); void present(DrmBuffer *buffer); QSize size() const { @@ -85,7 +90,10 @@ public: private: friend class DrmBackend; DrmBuffer(DrmBackend *backend, const QSize &size); + DrmBuffer(DrmBackend *backend, gbm_surface *surface); DrmBackend *m_backend; + gbm_surface *m_surface = nullptr; + gbm_bo *m_bo = nullptr; QSize m_size; quint32 m_handle = 0; quint32 m_bufferId = 0; diff --git a/egl_gbm_backend.cpp b/egl_gbm_backend.cpp new file mode 100644 index 0000000000..8302bfbdf5 --- /dev/null +++ b/egl_gbm_backend.cpp @@ -0,0 +1,276 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2015 Martin Gräßlin + +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_gbm_backend.h" +// kwin +#include "composite.h" +#include "drm_backend.h" +#include "options.h" +#include "screens.h" +// kwin libs +#include +// Qt +#include +// system +#include + +namespace KWin +{ + +EglGbmBackend::EglGbmBackend(DrmBackend *b) + : QObject(NULL) + , AbstractEglBackend() + , m_backend(b) +{ + initializeEgl(); + init(); + // Egl is always direct rendering + setIsDirectRendering(true); +} + +EglGbmBackend::~EglGbmBackend() +{ + // TODO: cleanup front buffer? + cleanup(); + if (m_gbmSurface) { + gbm_surface_destroy(m_gbmSurface); + } + if (m_device) { + gbm_device_destroy(m_device); + } +} + +bool EglGbmBackend::initializeEgl() +{ + initClientExtensions(); + EGLDisplay display = EGL_NO_DISPLAY; + + // Use eglGetPlatformDisplayEXT() to get the display pointer + // if the implementation supports it. + if (!hasClientExtension(QByteArrayLiteral("EGL_EXT_platform_base")) || + !hasClientExtension(QByteArrayLiteral("EGL_MESA_platform_gbm"))) { + setFailed("EGL_EXT_platform_base and/or EGL_MESA_platform_gbm missing"); + return false; + } + + m_device = gbm_create_device(m_backend->fd()); + if (!m_device) { + setFailed("Could not create gbm device"); + return false; + } + + display = eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_MESA, m_device, nullptr); + + if (display == EGL_NO_DISPLAY) + return false; + setEglDisplay(display); + return initEglAPI(); +} + +void EglGbmBackend::init() +{ + if (!initRenderingContext()) { + setFailed("Could not initialize rendering context"); + return; + } + + initKWinGL(); + initBufferAge(); + initWayland(); +} + +bool EglGbmBackend::initRenderingContext() +{ + initBufferConfigs(); + + EGLContext context = EGL_NO_CONTEXT; +#ifdef KWIN_HAVE_OPENGLES + const EGLint context_attribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + + context = eglCreateContext(eglDisplay(), config(), EGL_NO_CONTEXT, context_attribs); +#else + const EGLint context_attribs_31_core[] = { + EGL_CONTEXT_MAJOR_VERSION_KHR, 3, + EGL_CONTEXT_MINOR_VERSION_KHR, 1, + EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR, + EGL_NONE + }; + + const EGLint context_attribs_legacy[] = { + EGL_NONE + }; + + const char* eglExtensionsCString = eglQueryString(eglDisplay(), EGL_EXTENSIONS); + const QList extensions = QByteArray::fromRawData(eglExtensionsCString, qstrlen(eglExtensionsCString)).split(' '); + + // Try to create a 3.1 core context + if (options->glCoreProfile() && extensions.contains(QByteArrayLiteral("EGL_KHR_create_context"))) + context = eglCreateContext(eglDisplay(), config(), EGL_NO_CONTEXT, context_attribs_31_core); + + if (context == EGL_NO_CONTEXT) + context = eglCreateContext(eglDisplay(), config(), EGL_NO_CONTEXT, context_attribs_legacy); +#endif + + if (context == EGL_NO_CONTEXT) { + qCCritical(KWIN_CORE) << "Create Context failed"; + return false; + } + setContext(context); + + EGLSurface surface = EGL_NO_SURFACE; + m_gbmSurface = gbm_surface_create(m_device, m_backend->size().width(), m_backend->size().height(), + GBM_FORMAT_XRGB8888, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); + if (!m_gbmSurface) { + qCCritical(KWIN_CORE) << "Create gbm surface failed"; + return false; + } + surface = eglCreatePlatformWindowSurfaceEXT(eglDisplay(), config(), (void *) m_gbmSurface, nullptr); + + if (surface == EGL_NO_SURFACE) { + qCCritical(KWIN_CORE) << "Create Window Surface failed"; + return false; + } + setSurface(surface); + + return makeContextCurrent(); +} + +bool EglGbmBackend::makeContextCurrent() +{ + if (eglMakeCurrent(eglDisplay(), surface(), surface(), context()) == EGL_FALSE) { + qCCritical(KWIN_CORE) << "Make Context Current failed"; + return false; + } + + EGLint error = eglGetError(); + if (error != EGL_SUCCESS) { + qCWarning(KWIN_CORE) << "Error occurred while creating context " << error; + return false; + } + return true; +} + +bool EglGbmBackend::initBufferConfigs() +{ + const EGLint config_attribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RED_SIZE, 1, + EGL_GREEN_SIZE, 1, + EGL_BLUE_SIZE, 1, + EGL_ALPHA_SIZE, 0, +#ifdef KWIN_HAVE_OPENGLES + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, +#else + EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, +#endif + EGL_CONFIG_CAVEAT, EGL_NONE, + EGL_NONE, + }; + + EGLint count; + EGLConfig configs[1024]; + if (eglChooseConfig(eglDisplay(), config_attribs, configs, 1, &count) == EGL_FALSE) { + qCCritical(KWIN_CORE) << "choose config failed"; + return false; + } + if (count != 1) { + qCCritical(KWIN_CORE) << "choose config did not return a config" << count; + return false; + } + setConfig(configs[0]); + + return true; +} + +void EglGbmBackend::present() +{ + eglSwapBuffers(eglDisplay(), surface()); + auto oldBuffer = m_frontBuffer; + m_frontBuffer = m_backend->createBuffer(m_gbmSurface); + m_backend->present(m_frontBuffer); + delete oldBuffer; + if (supportsBufferAge()) { + eglQuerySurface(eglDisplay(), surface(), EGL_BUFFER_AGE_EXT, &m_bufferAge); + } +} + +void EglGbmBackend::screenGeometryChanged(const QSize &size) +{ + Q_UNUSED(size) + // TODO, create new buffer? +} + +SceneOpenGL::TexturePrivate *EglGbmBackend::createBackendTexture(SceneOpenGL::Texture *texture) +{ + return new EglGbmTexture(texture, this); +} + +QRegion EglGbmBackend::prepareRenderingFrame() +{ + QRegion repaint; + if (supportsBufferAge()) + repaint = accumulatedDamageHistory(m_bufferAge); + startRenderTimer(); + return repaint; +} + +void EglGbmBackend::endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) +{ + if (damagedRegion.isEmpty()) { + + // If the damaged region of a window is fully occluded, the only + // rendering done, if any, will have been to repair a reused back + // buffer, making it identical to the front buffer. + // + // In this case we won't post the back buffer. Instead we'll just + // set the buffer age to 1, so the repaired regions won't be + // rendered again in the next frame. + if (!renderedRegion.isEmpty()) + glFlush(); + + m_bufferAge = 1; + return; + } + present(); + + // Save the damaged region to history + if (supportsBufferAge()) + addToDamageHistory(damagedRegion); +} + +bool EglGbmBackend::usesOverlayWindow() const +{ + return false; +} + +/************************************************ + * EglTexture + ************************************************/ + +EglGbmTexture::EglGbmTexture(KWin::SceneOpenGL::Texture *texture, EglGbmBackend *backend) + : AbstractEglTexture(texture, backend) +{ +} + +EglGbmTexture::~EglGbmTexture() = default; + +} // namespace diff --git a/egl_gbm_backend.h b/egl_gbm_backend.h new file mode 100644 index 0000000000..2b440e1dbf --- /dev/null +++ b/egl_gbm_backend.h @@ -0,0 +1,80 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2015 Martin Gräßlin + +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_GBM_BACKEND_H +#define KWIN_EGL_GBM_BACKEND_H +#include "abstract_egl_backend.h" +#include "scene_opengl.h" + +struct gbm_device; +struct gbm_surface; + +namespace KWin +{ +class DrmBackend; +class DrmBuffer; + +/** + * @brief OpenGL Backend using Egl on a GBM surface. + **/ +class EglGbmBackend : public QObject, public AbstractEglBackend +{ + Q_OBJECT +public: + EglGbmBackend(DrmBackend *b); + virtual ~EglGbmBackend(); + void screenGeometryChanged(const QSize &size) override; + SceneOpenGL::TexturePrivate *createBackendTexture(SceneOpenGL::Texture *texture) override; + QRegion prepareRenderingFrame() override; + void endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) override; + bool usesOverlayWindow() const override; + +protected: + void present() override; + +private: + void init(); + bool initializeEgl(); + bool initBufferConfigs(); + bool initRenderingContext(); + bool makeContextCurrent(); + DrmBackend *m_backend; + gbm_device *m_device = nullptr; + gbm_surface *m_gbmSurface = nullptr; + DrmBuffer *m_frontBuffer = nullptr; + int m_bufferAge = 0; + friend class EglGbmTexture; +}; + +/** + * @brief Texture using an EGLImageKHR. + **/ +class EglGbmTexture : public AbstractEglTexture +{ +public: + virtual ~EglGbmTexture(); + +private: + friend class EglGbmBackend; + EglGbmTexture(SceneOpenGL::Texture *texture, EglGbmBackend *backend); +}; + +} // namespace + +#endif diff --git a/workspace.cpp b/workspace.cpp index 4b81a83cee..4b0189b988 100644 --- a/workspace.cpp +++ b/workspace.cpp @@ -1338,6 +1338,12 @@ QString Workspace::supportInformation() const support.append(yes); #else support.append(no); +#endif + support.append(QStringLiteral("HAVE_GBM: ")); +#if HAVE_GBM + support.append(yes); +#else + support.append(no); #endif support.append(QStringLiteral("HAVE_XCB_CURSOR: ")); #if HAVE_XCB_CURSOR