diff --git a/TESTING.md b/TESTING.md index ea24a0bbe..a8a764212 100644 --- a/TESTING.md +++ b/TESTING.md @@ -1,32 +1,33 @@ # Testing in KWin KWin provides a unit and integration test suit for X11 and Wayland. The source code for the tests can be found in the subdirectory autotests. The test suite should be run prior to any merge to KWin. # Dependencies The following additional software needs to be installed for running the test suite: * Xvfb * Xephyr * glxgears * DMZ-white cursor theme * breeze window decoration # Preparing a run of the test suite -Please load the kernel module "vgem". This is required to provide a virtual OpenGL device. +In case your system does not support the EGL extension EGL_MESA_platform_surfaceless, +please load the kernel module "vgem". This is required to provide a virtual OpenGL device. sudo modprobe vgem Furthermore the user executing the test suite must be able to read and write to the dri device created by vgem. # Running the test suite The test suite can be run from the build directory. Best is to do: cd path/to/build/directory xvfb-run ctest # Running individual tests All tests executables are created in the directory "bin" in the build directory. Each test can be executed by just starting it from within the test directory. To prevent side effects with the running session it is recommended to start a dedicated dbus session: cd path/to/build/directory/bin dbus-run-session ./testFoo For tests relying on X11 one should also either start a dedicated Xvfb and export DISPLAY or use xvfb-run as described above. diff --git a/plugins/platforms/virtual/egl_gbm_backend.cpp b/plugins/platforms/virtual/egl_gbm_backend.cpp index cce4d3d4d..36ba67eb1 100644 --- a/plugins/platforms/virtual/egl_gbm_backend.cpp +++ b/plugins/platforms/virtual/egl_gbm_backend.cpp @@ -1,293 +1,300 @@ /******************************************************************** 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 "virtual_backend.h" #include "options.h" #include "screens.h" #include "udev.h" #include // kwin libs #include #include // Qt #include // system #include #include #if HAVE_GBM #include #endif namespace KWin { EglGbmBackend::EglGbmBackend(VirtualBackend *b) : AbstractEglBackend() , m_backend(b) { // Egl is always direct rendering setIsDirectRendering(true); } EglGbmBackend::~EglGbmBackend() { while (GLRenderTarget::isRenderTargetBound()) { GLRenderTarget::popRenderTarget(); } delete m_fbo; delete m_backBuffer; cleanup(); } void EglGbmBackend::initGbmDevice() { if (m_backend->drmFd() != -1) { // already initialized return; } QScopedPointer udev(new Udev); UdevDevice::Ptr device = udev->virtualGpu(); if (!device) { // if we don't have a virtual (vgem) device, try to find a render node qCDebug(KWIN_VIRTUAL) << "No vgem device, looking for a render node"; device = udev->renderNode(); } if (!device) { qCDebug(KWIN_VIRTUAL) << "Neither a render node, nor a vgem device found"; return; } qCDebug(KWIN_VIRTUAL) << "Found a device: " << device->devNode(); int fd = open(device->devNode(), O_RDWR | O_CLOEXEC); if (fd == -1) { qCWarning(KWIN_VIRTUAL) << "Failed to open: " << device->devNode(); return; } m_backend->setDrmFd(fd); #if HAVE_GBM auto gbmDevice = gbm_create_device(fd); if (!gbmDevice) { qCWarning(KWIN_VIRTUAL) << "Failed to open gbm device"; } m_backend->setGbmDevice(gbmDevice); #endif } bool EglGbmBackend::initializeEgl() { initClientExtensions(); EGLDisplay display = m_backend->sceneEglDisplay(); // Use eglGetPlatformDisplayEXT() to get the display pointer // if the implementation supports it. if (display == EGL_NO_DISPLAY) { + // first try surfaceless + if (hasClientExtension(QByteArrayLiteral("EGL_MESA_platform_surfaceless"))) { + display = eglGetPlatformDisplayEXT(EGL_PLATFORM_SURFACELESS_MESA, EGL_DEFAULT_DISPLAY, nullptr); + } + } + if (display == EGL_NO_DISPLAY) { + qCDebug(KWIN_VIRTUAL) << "Failed to create surfaceless platform, trying with vgem device"; const bool hasMesaGBM = hasClientExtension(QByteArrayLiteral("EGL_MESA_platform_gbm")); const bool hasKHRGBM = hasClientExtension(QByteArrayLiteral("EGL_KHR_platform_gbm")); const GLenum platform = hasMesaGBM ? EGL_PLATFORM_GBM_MESA : EGL_PLATFORM_GBM_KHR; if (!hasClientExtension(QByteArrayLiteral("EGL_EXT_platform_base")) || (!hasMesaGBM && !hasKHRGBM)) { setFailed("missing one or more extensions between EGL_EXT_platform_base, EGL_MESA_platform_gbm, EGL_KHR_platform_gbm"); return false; } #if HAVE_GBM initGbmDevice(); if (auto device = m_backend->gbmDevice()) { display = eglGetPlatformDisplayEXT(platform, device, nullptr); } #endif if (display == EGL_NO_DISPLAY) { qCWarning(KWIN_VIRTUAL) << "Failed to create EGLDisplay through GBM device, trying with default device"; display = eglGetPlatformDisplayEXT(platform, EGL_DEFAULT_DISPLAY, nullptr); } } if (display == EGL_NO_DISPLAY) return false; setEglDisplay(display); return initEglAPI(); } void EglGbmBackend::init() { if (!initializeEgl()) { setFailed("Could not initialize egl"); return; } if (!initRenderingContext()) { setFailed("Could not initialize rendering context"); return; } initKWinGL(); m_backBuffer = new GLTexture(GL_RGB8, screens()->size().width(), screens()->size().height()); m_fbo = new GLRenderTarget(*m_backBuffer); if (!m_fbo->valid()) { setFailed("Could not create framebuffer object"); return; } GLRenderTarget::pushRenderTarget(m_fbo); if (!m_fbo->isRenderTargetBound()) { setFailed("Failed to bind framebuffer object"); return; } if (checkGLError("Init")) { setFailed("Error during init of EglGbmBackend"); return; } setSupportsBufferAge(false); initWayland(); } bool EglGbmBackend::initRenderingContext() { initBufferConfigs(); const char* eglExtensionsCString = eglQueryString(eglDisplay(), EGL_EXTENSIONS); const QList extensions = QByteArray::fromRawData(eglExtensionsCString, qstrlen(eglExtensionsCString)).split(' '); if (!extensions.contains(QByteArrayLiteral("EGL_KHR_surfaceless_context"))) { return false; } if (!createContext()) { return false; } setSurfaceLessContext(true); return makeCurrent(); } 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, EGL_RENDERABLE_TYPE, isOpenGLES() ? EGL_OPENGL_ES2_BIT : EGL_OPENGL_BIT, EGL_CONFIG_CAVEAT, EGL_NONE, EGL_NONE, }; EGLint count; EGLConfig configs[1024]; if (eglChooseConfig(eglDisplay(), config_attribs, configs, 1, &count) == EGL_FALSE) { return false; } if (count != 1) { return false; } setConfig(configs[0]); return true; } void EglGbmBackend::present() { } void EglGbmBackend::screenGeometryChanged(const QSize &size) { Q_UNUSED(size) // TODO, create new buffer? } SceneOpenGLTexturePrivate *EglGbmBackend::createBackendTexture(SceneOpenGLTexture *texture) { return new EglGbmTexture(texture, this); } QRegion EglGbmBackend::prepareRenderingFrame() { startRenderTimer(); if (!GLRenderTarget::isRenderTargetBound()) { GLRenderTarget::pushRenderTarget(m_fbo); } return QRegion(0, 0, screens()->size().width(), screens()->size().height()); } static void convertFromGLImage(QImage &img, int w, int h) { // from QtOpenGL/qgl.cpp // Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) // see http://qt.gitorious.org/qt/qt/blobs/master/src/opengl/qgl.cpp if (QSysInfo::ByteOrder == QSysInfo::BigEndian) { // OpenGL gives RGBA; Qt wants ARGB uint *p = (uint*)img.bits(); uint *end = p + w * h; while (p < end) { uint a = *p << 24; *p = (*p >> 8) | a; p++; } } else { // OpenGL gives ABGR (i.e. RGBA backwards); Qt wants ARGB for (int y = 0; y < h; y++) { uint *q = (uint*)img.scanLine(y); for (int x = 0; x < w; ++x) { const uint pixel = *q; *q = ((pixel << 16) & 0xff0000) | ((pixel >> 16) & 0xff) | (pixel & 0xff00ff00); q++; } } } img = img.mirrored(); } void EglGbmBackend::endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) { Q_UNUSED(renderedRegion) Q_UNUSED(damagedRegion) glFlush(); if (m_backend->saveFrames()) { QImage img = QImage(QSize(m_backBuffer->width(), m_backBuffer->height()), QImage::Format_ARGB32); glReadnPixels(0, 0, m_backBuffer->width(), m_backBuffer->height(), GL_RGBA, GL_UNSIGNED_BYTE, img.byteCount(), (GLvoid*)img.bits()); convertFromGLImage(img, m_backBuffer->width(), m_backBuffer->height()); img.save(QStringLiteral("%1/%2.png").arg(m_backend->saveFrames()).arg(QString::number(m_frameCounter++))); } GLRenderTarget::popRenderTarget(); } bool EglGbmBackend::usesOverlayWindow() const { return false; } /************************************************ * EglTexture ************************************************/ EglGbmTexture::EglGbmTexture(KWin::SceneOpenGLTexture *texture, EglGbmBackend *backend) : AbstractEglTexture(texture, backend) { } EglGbmTexture::~EglGbmTexture() = default; } // namespace