/******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013 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 . *********************************************************************/ #define WL_EGL_PLATFORM 1 #include "egl_wayland_backend.h" // kwin #include "options.h" #include "wayland_backend.h" // kwin libs #include // KDE #include // Qt #include // system #include #include namespace KWin { EglWaylandBackend::EglWaylandBackend() : QObject(NULL) , OpenGLBackend() , m_context(EGL_NO_CONTEXT) , m_bufferAge(0) , m_wayland(Wayland::WaylandBackend::self()) , m_overlay(NULL) { if (!m_wayland) { setFailed("Wayland Backend has not been created"); return; } qDebug() << "Connected to Wayland display?" << (m_wayland->display() ? "yes" : "no" ); if (!m_wayland->display()) { setFailed("Could not connect to Wayland compositor"); return; } connect(m_wayland, SIGNAL(shellSurfaceSizeChanged(QSize)), SLOT(overlaySizeChanged(QSize))); initializeEgl(); init(); // Egl is always direct rendering setIsDirectRendering(true); qWarning() << "Using Wayland rendering backend"; qWarning() << "This is a highly experimental backend, do not use for productive usage!"; qWarning() << "Please do not report any issues you might encounter when using this backend!"; } EglWaylandBackend::~EglWaylandBackend() { cleanupGL(); doneCurrent(); eglDestroyContext(m_display, m_context); eglDestroySurface(m_display, m_surface); eglTerminate(m_display); eglReleaseThread(); if (m_overlay) { wl_egl_window_destroy(m_overlay); } } bool EglWaylandBackend::initializeEgl() { m_display = eglGetDisplay(m_wayland->display()); if (m_display == EGL_NO_DISPLAY) return false; EGLint major, minor; if (eglInitialize(m_display, &major, &minor) == EGL_FALSE) return false; EGLint error = eglGetError(); if (error != EGL_SUCCESS) { qWarning() << "Error during eglInitialize " << error; return false; } qDebug() << "Egl Initialize succeeded"; #ifdef KWIN_HAVE_OPENGLES eglBindAPI(EGL_OPENGL_ES_API); #else if (eglBindAPI(EGL_OPENGL_API) == EGL_FALSE) { qCritical() << "bind OpenGL API failed"; return false; } #endif qDebug() << "EGL version: " << major << "." << minor; return true; } void EglWaylandBackend::init() { if (!initRenderingContext()) { setFailed("Could not initialize rendering context"); return; } initEGL(); GLPlatform *glPlatform = GLPlatform::instance(); glPlatform->detect(EglPlatformInterface); glPlatform->printResults(); initGL(EglPlatformInterface); setSupportsBufferAge(false); if (hasGLExtension("EGL_EXT_buffer_age")) { const QByteArray useBufferAge = qgetenv("KWIN_USE_BUFFER_AGE"); if (useBufferAge != "0") setSupportsBufferAge(true); } } bool EglWaylandBackend::initRenderingContext() { initBufferConfigs(); #ifdef KWIN_HAVE_OPENGLES const EGLint context_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; m_context = eglCreateContext(m_display, m_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 QByteArray eglExtensions = eglQueryString(m_display, EGL_EXTENSIONS); const QList extensions = eglExtensions.split(' '); // Try to create a 3.1 core context if (options->glCoreProfile() && extensions.contains("EGL_KHR_create_context")) m_context = eglCreateContext(m_display, m_config, EGL_NO_CONTEXT, context_attribs_31_core); if (m_context == EGL_NO_CONTEXT) m_context = eglCreateContext(m_display, m_config, EGL_NO_CONTEXT, context_attribs_legacy); #endif if (m_context == EGL_NO_CONTEXT) { qCritical() << "Create Context failed"; return false; } if (!m_wayland->surface()) { return false; } const QSize &size = m_wayland->shellSurfaceSize(); m_overlay = wl_egl_window_create(m_wayland->surface(), size.width(), size.height()); if (!m_overlay) { qCritical() << "Creating Wayland Egl window failed"; return false; } m_surface = eglCreateWindowSurface(m_display, m_config, m_overlay, NULL); if (m_surface == EGL_NO_SURFACE) { qCritical() << "Create Window Surface failed"; return false; } return makeContextCurrent(); } bool EglWaylandBackend::makeContextCurrent() { if (eglMakeCurrent(m_display, m_surface, m_surface, m_context) == EGL_FALSE) { qCritical() << "Make Context Current failed"; return false; } EGLint error = eglGetError(); if (error != EGL_SUCCESS) { qWarning() << "Error occurred while creating context " << error; return false; } return true; } bool EglWaylandBackend::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(m_display, config_attribs, configs, 1, &count) == EGL_FALSE) { qCritical() << "choose config failed"; return false; } if (count != 1) { qCritical() << "choose config did not return a config" << count; return false; } m_config = configs[0]; return true; } void EglWaylandBackend::present() { // need to dispatch pending events as eglSwapBuffers can block m_wayland->dispatchEvents(); if (supportsBufferAge()) { eglSwapBuffers(m_display, m_surface); eglQuerySurface(m_display, m_surface, EGL_BUFFER_AGE_EXT, &m_bufferAge); setLastDamage(QRegion()); return; } else { eglSwapBuffers(m_display, m_surface); setLastDamage(QRegion()); } } void EglWaylandBackend::screenGeometryChanged(const QSize &size) { Q_UNUSED(size) // no backend specific code needed // TODO: base implementation in OpenGLBackend // The back buffer contents are now undefined m_bufferAge = 0; } SceneOpenGL::TexturePrivate *EglWaylandBackend::createBackendTexture(SceneOpenGL::Texture *texture) { return new EglWaylandTexture(texture, this); } QRegion EglWaylandBackend::prepareRenderingFrame() { if (!lastDamage().isEmpty()) present(); QRegion repaint; if (supportsBufferAge()) repaint = accumulatedDamageHistory(m_bufferAge); eglWaitNative(EGL_CORE_NATIVE_ENGINE); startRenderTimer(); return repaint; } void EglWaylandBackend::endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) { if (damagedRegion.isEmpty()) { setLastDamage(QRegion()); // 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; } setLastDamage(renderedRegion); if (!blocksForRetrace()) { // This also sets lastDamage to empty which prevents the frame from // being posted again when prepareRenderingFrame() is called. present(); } else { // Make sure that the GPU begins processing the command stream // now and not the next time prepareRenderingFrame() is called. glFlush(); } // Save the damaged region to history if (supportsBufferAge()) addToDamageHistory(damagedRegion); } bool EglWaylandBackend::makeCurrent() { if (QOpenGLContext *context = QOpenGLContext::currentContext()) { // Workaround to tell Qt that no QOpenGLContext is current context->doneCurrent(); } const bool current = eglMakeCurrent(m_display, m_surface, m_surface, m_context); return current; } void EglWaylandBackend::doneCurrent() { eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } Shm *EglWaylandBackend::shm() { if (m_shm.isNull()) { m_shm.reset(new Shm); } return m_shm.data(); } void EglWaylandBackend::overlaySizeChanged(const QSize &size) { wl_egl_window_resize(m_overlay, size.width(), size.height(), 0, 0); } /************************************************ * EglTexture ************************************************/ EglWaylandTexture::EglWaylandTexture(KWin::SceneOpenGL::Texture *texture, KWin::EglWaylandBackend *backend) : SceneOpenGL::TexturePrivate() , q(texture) , m_backend(backend) , m_referencedPixmap(XCB_PIXMAP_NONE) { m_target = GL_TEXTURE_2D; } EglWaylandTexture::~EglWaylandTexture() { } OpenGLBackend *EglWaylandTexture::backend() { return m_backend; } void EglWaylandTexture::findTarget() { if (m_target != GL_TEXTURE_2D) { m_target = GL_TEXTURE_2D; } } bool EglWaylandTexture::loadTexture(const Pixmap &pix, const QSize &size, int depth) { // HACK: egl wayland platform doesn't support texture from X11 pixmap through the KHR_image_pixmap // extension. To circumvent this problem we copy the pixmap content into a SHM image and from there // to the OpenGL texture. This is a temporary solution. In future we won't need to get the content // from X11 pixmaps. That's what we have XWayland for to get the content into a nice Wayland buffer. Q_UNUSED(depth) if (pix == XCB_PIXMAP_NONE) return false; m_referencedPixmap = pix; Shm *shm = m_backend->shm(); if (!shm->isValid()) { return false; } xcb_shm_get_image_cookie_t cookie = xcb_shm_get_image_unchecked(connection(), pix, 0, 0, size.width(), size.height(), ~0, XCB_IMAGE_FORMAT_Z_PIXMAP, shm->segment(), 0); glGenTextures(1, &m_texture); q->setWrapMode(GL_CLAMP_TO_EDGE); q->setFilter(GL_LINEAR); q->bind(); ScopedCPointer image(xcb_shm_get_image_reply(connection(), cookie, NULL)); if (image.isNull()) { return false; } // TODO: other formats #ifndef KWIN_HAVE_OPENGLES glTexImage2D(m_target, 0, GL_RGBA8, size.width(), size.height(), 0, GL_BGRA, GL_UNSIGNED_BYTE, shm->buffer()); #endif q->unbind(); checkGLError("load texture"); q->setYInverted(true); m_size = size; updateMatrix(); return true; } bool EglWaylandTexture::update(const QRegion &damage) { if (m_referencedPixmap == XCB_PIXMAP_NONE) { return false; } Shm *shm = m_backend->shm(); if (!shm->isValid()) { return false; } // TODO: optimize by only updating the damaged areas const QRect &damagedRect = damage.boundingRect(); xcb_shm_get_image_cookie_t cookie = xcb_shm_get_image_unchecked(connection(), m_referencedPixmap, damagedRect.x(), damagedRect.y(), damagedRect.width(), damagedRect.height(), ~0, XCB_IMAGE_FORMAT_Z_PIXMAP, shm->segment(), 0); q->bind(); ScopedCPointer image(xcb_shm_get_image_reply(connection(), cookie, NULL)); if (image.isNull()) { return false; } // TODO: other formats #ifndef KWIN_HAVE_OPENGLES glTexSubImage2D(m_target, 0, damagedRect.x(), damagedRect.y(), damagedRect.width(), damagedRect.height(), GL_BGRA, GL_UNSIGNED_BYTE, shm->buffer()); #endif q->unbind(); checkGLError("update texture"); return true; } Shm::Shm() : m_shmId(-1) , m_buffer(NULL) , m_segment(XCB_NONE) , m_valid(false) { m_valid = init(); } Shm::~Shm() { if (m_valid) { xcb_shm_detach(connection(), m_segment); shmdt(m_buffer); } } bool Shm::init() { const xcb_query_extension_reply_t *ext = xcb_get_extension_data(connection(), &xcb_shm_id); if (!ext || !ext->present) { qDebug() << "SHM extension not available"; return false; } ScopedCPointer version(xcb_shm_query_version_reply(connection(), xcb_shm_query_version_unchecked(connection()), NULL)); if (version.isNull()) { qDebug() << "Failed to get SHM extension version information"; return false; } const int MAXSIZE = 4096 * 2048 * 4; // TODO check there are not larger windows m_shmId = shmget(IPC_PRIVATE, MAXSIZE, IPC_CREAT | 0600); if (m_shmId < 0) { qDebug() << "Failed to allocate SHM segment"; return false; } m_buffer = shmat(m_shmId, NULL, 0 /*read/write*/); if (-1 == reinterpret_cast(m_buffer)) { qDebug() << "Failed to attach SHM segment"; shmctl(m_shmId, IPC_RMID, NULL); return false; } shmctl(m_shmId, IPC_RMID, NULL); m_segment = xcb_generate_id(connection()); const xcb_void_cookie_t cookie = xcb_shm_attach_checked(connection(), m_segment, m_shmId, false); ScopedCPointer error(xcb_request_check(connection(), cookie)); if (!error.isNull()) { qDebug() << "xcb_shm_attach error: " << error->error_code; shmdt(m_buffer); return false; } return true; } } // namespace