From d2452e29175f1140c2c34bb5a847759e399242ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Gr=C3=A4=C3=9Flin?= Date: Thu, 6 Mar 2014 09:43:52 +0100 Subject: [PATCH] EGL/X11 backend for WindowThumbnail QQuickItem Introduces a new optional dependency to EGL. If EGL is found the WindowThumbnail gets compiled with EGL support. The EGL support is unlike Qt's XCB plugin not mutual exclusive to the GLX backend, thus it's possible to compile with EGL and GLX at the same time. By that this implementation is prepared for the case that Qt supports EGL or GLX through runtime selection. In practice EGL support is only useful if Qt is compiled with GLESv2. In that case the corebindingsplugin gets linked against Qt5::Gui_GLESv2 to get the dependency to GLES. The implementation makes use of the EGL_KHR_image extension (or EGL_KHR_image_base and EGL_KHR_Image_pixmap) and the GL_OES_EGL_image extension to bind the X pixmap to an EGLImageKHR. REVIEW: 116627 --- CMakeLists.txt | 8 + src/declarativeimports/core/CMakeLists.txt | 8 + .../core/windowthumbnail.cpp | 151 +++++++++++++++--- src/declarativeimports/core/windowthumbnail.h | 22 ++- src/plasma/config-plasma.h.cmake | 1 + 5 files changed, 164 insertions(+), 26 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 357aaa9bc..115c930bb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -102,6 +102,14 @@ set_package_properties(OpenGL PROPERTIES DESCRIPTION "The OpenGL libraries" URL "http://www.opengl.org" TYPE OPTIONAL ) + +find_package(EGL) +set_package_properties(EGL PROPERTIES + PURPOSE "Support for Window Thumbnail on EGL platform" + TYPE OPTIONAL + ) +set(HAVE_EGL ${EGL_FOUND}) + if(OPENGL_FOUND AND (${Qt5Gui_OPENGL_IMPLEMENTATION} STREQUAL "GL")) set(HAVE_GLX ${HAVE_X11}) else() diff --git a/src/declarativeimports/core/CMakeLists.txt b/src/declarativeimports/core/CMakeLists.txt index 97e928303..fb518034f 100644 --- a/src/declarativeimports/core/CMakeLists.txt +++ b/src/declarativeimports/core/CMakeLists.txt @@ -66,6 +66,14 @@ if(HAVE_X11) endif() endif() +if(HAVE_EGL) + target_link_libraries(corebindingsplugin EGL::EGL) +endif() + +if (${Qt5Gui_OPENGL_IMPLEMENTATION} STREQUAL "GLESv2") + target_link_libraries(corebindingsplugin Qt5::Gui_GLESv2) +endif() + install(TARGETS corebindingsplugin DESTINATION ${QML_INSTALL_DIR}/org/kde/plasma/core) install(FILES qmldir DESTINATION ${QML_INSTALL_DIR}/org/kde/plasma/core) diff --git a/src/declarativeimports/core/windowthumbnail.cpp b/src/declarativeimports/core/windowthumbnail.cpp index b96d939b2..e1bda70c3 100644 --- a/src/declarativeimports/core/windowthumbnail.cpp +++ b/src/declarativeimports/core/windowthumbnail.cpp @@ -34,6 +34,11 @@ typedef void (*glXBindTexImageEXT_func)(Display* dpy, GLXDrawable drawable, int buffer, const int* attrib_list); typedef void (*glXReleaseTexImageEXT_func)(Display* dpy, GLXDrawable drawable, int buffer); #endif +#if HAVE_EGL +typedef EGLImageKHR(*eglCreateImageKHR_func)(EGLDisplay, EGLContext, EGLenum, EGLClientBuffer, const EGLint*); +typedef EGLBoolean(*eglDestroyImageKHR_func)(EGLDisplay, EGLImageKHR); +typedef GLvoid(*glEGLImageTargetTexture2DOES_func)(GLenum, GLeglImageOES); +#endif // HAVE_EGL #endif namespace Plasma { @@ -67,7 +72,11 @@ WindowThumbnail::WindowThumbnail(QQuickItem* parent) , m_texture(0) #if HAVE_GLX , m_glxPixmap(XCB_PIXMAP_NONE) -#endif +#endif // HAVE_GLX +#if HAVE_EGL + , m_eglFunctionsResolved(false) + , m_image(EGL_NO_IMAGE_KHR) +#endif // HAVE_EGL #endif { setFlag(ItemHasContents); @@ -189,36 +198,23 @@ void WindowThumbnail::iconToTexture(WindowTextureNode *textureNode) textureNode->reset(window()->createTextureFromImage(image)); } -void WindowThumbnail::windowToTexture(WindowTextureNode *textureNode) -{ - if (!m_damaged && textureNode->texture()) { - return; - } #if HAVE_XCB_COMPOSITE - xcb_connection_t *c = QX11Info::connection(); - if (m_pixmap == XCB_PIXMAP_NONE) { - m_pixmap = pixmapForWindow(); - } - if (m_pixmap == XCB_PIXMAP_NONE) { - // create above failed - iconToTexture(textureNode); - return; - } #if HAVE_GLX +bool WindowThumbnail::windowToTextureGLX(WindowTextureNode *textureNode) +{ if (glXGetCurrentContext()) { if (!m_openGLFunctionsResolved) { resolveGLXFunctions(); } if (!m_bindTexImage || !m_releaseTexImage) { - iconToTexture(textureNode); - return; + return false; } if (m_glxPixmap == XCB_PIXMAP_NONE) { + xcb_connection_t *c = QX11Info::connection(); auto geometryCookie = xcb_get_geometry_unchecked(c, m_pixmap); if (!loadGLXTexture()) { - iconToTexture(textureNode); - return; + return false; } QScopedPointer geo(xcb_get_geometry_reply(c, geometryCookie, Q_NULLPTR)); @@ -231,14 +227,114 @@ void WindowThumbnail::windowToTexture(WindowTextureNode *textureNode) } textureNode->texture()->bind(); bindGLXTexture(); - } else { -#else - { -#endif + return true; + } + return false; +} +#endif // HAVE_GLX + +#if HAVE_EGL +bool WindowThumbnail::xcbWindowToTextureEGL(WindowTextureNode *textureNode) +{ + EGLContext context = eglGetCurrentContext(); + if (context != EGL_NO_CONTEXT) { + if (!m_eglFunctionsResolved) { + resolveEGLFunctions(); + } + if (!m_eglCreateImageKHR || !m_eglDestroyImageKHR || !m_glEGLImageTargetTexture2DOES) { + return false; + } + if (m_image == EGL_NO_IMAGE_KHR) { + xcb_connection_t *c = QX11Info::connection(); + auto geometryCookie = xcb_get_geometry_unchecked(c, m_pixmap); + + const EGLint attribs[] = { + EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, + EGL_NONE + }; + m_image = ((eglCreateImageKHR_func)(m_eglCreateImageKHR))(eglGetCurrentDisplay(), EGL_NO_CONTEXT, + EGL_NATIVE_PIXMAP_KHR, + (EGLClientBuffer)m_pixmap, attribs); + + if (m_image == EGL_NO_IMAGE_KHR) { + qDebug() << "failed to create egl image"; + return false; + } + + glGenTextures(1, &m_texture); + QScopedPointer geo(xcb_get_geometry_reply(c, geometryCookie, Q_NULLPTR)); + QSize size; + if (!geo.isNull()) { + size.setWidth(geo->width); + size.setHeight(geo->height); + } + textureNode->reset(window()->createTextureFromId(m_texture, size)); + } + textureNode->texture()->bind(); + bindEGLTexture(); + return true; + } + return false; +} + +void WindowThumbnail::resolveEGLFunctions() +{ + EGLDisplay display = eglGetCurrentDisplay(); + if (display == EGL_NO_DISPLAY) { + return; + } + auto *context = window()->openglContext(); + QList extensions = QByteArray(eglQueryString(display, EGL_EXTENSIONS)).split(' '); + if (extensions.contains(QByteArrayLiteral("EGL_KHR_image")) || + (extensions.contains(QByteArrayLiteral("EGL_KHR_image_base")) && + extensions.contains(QByteArrayLiteral("EGL_KHR_image_pixmap")))) { + if (context->hasExtension(QByteArrayLiteral("GL_OES_EGL_image"))) { + qDebug() << "Have EGL texture from pixmap"; + m_eglCreateImageKHR = context->getProcAddress(QByteArrayLiteral("eglCreateImageKHR")); + m_eglDestroyImageKHR = context->getProcAddress(QByteArrayLiteral("eglDestroyImageKHR")); + m_glEGLImageTargetTexture2DOES = context->getProcAddress(QByteArrayLiteral("glEGLImageTargetTexture2DOES")); + } + } + m_eglFunctionsResolved = true; +} + +void WindowThumbnail::bindEGLTexture() +{ + ((glEGLImageTargetTexture2DOES_func)(m_glEGLImageTargetTexture2DOES))(GL_TEXTURE_2D, (GLeglImageOES)m_image); + resetDamaged(); +} +#endif // HAVE_EGL + +#endif // HAVE_XCB_COMPOSITE + +void WindowThumbnail::windowToTexture(WindowTextureNode *textureNode) +{ + if (!m_damaged && textureNode->texture()) { + return; + } +#if HAVE_XCB_COMPOSITE + if (m_pixmap == XCB_PIXMAP_NONE) { + m_pixmap = pixmapForWindow(); + } + if (m_pixmap == XCB_PIXMAP_NONE) { + // create above failed + iconToTexture(textureNode); + return; + } + bool fallbackToIcon = true; +#if HAVE_GLX + fallbackToIcon = !windowToTextureGLX(textureNode); +#endif // HAVE_GLX +#if HAVE_EGL + if (fallbackToIcon) { + // if glx succeeded fallbackToIcon is false, thus we shouldn't try egl + fallbackToIcon = !xcbWindowToTextureEGL(textureNode); + } +#endif // HAVE_EGL + if (fallbackToIcon) { // just for safety to not crash iconToTexture(textureNode); } - // TODO: add an egl variant textureNode->markDirty(QSGNode::DirtyForceUpdate); #endif } @@ -393,6 +489,13 @@ void WindowThumbnail::discardPixmap() m_glxPixmap = XCB_PIXMAP_NONE; glDeleteTextures(1, &m_texture); } +#endif +#if HAVE_EGL + if (m_image != EGL_NO_IMAGE_KHR) { + ((eglDestroyImageKHR_func)(m_eglDestroyImageKHR))(eglGetCurrentDisplay(), m_image); + m_image = EGL_NO_IMAGE_KHR; + glDeleteTextures(1, &m_texture); + } #endif if (m_pixmap != XCB_WINDOW_NONE) { xcb_free_pixmap(QX11Info::connection(), m_pixmap); diff --git a/src/declarativeimports/core/windowthumbnail.h b/src/declarativeimports/core/windowthumbnail.h index 1d4595393..aa10e7ebe 100644 --- a/src/declarativeimports/core/windowthumbnail.h +++ b/src/declarativeimports/core/windowthumbnail.h @@ -26,8 +26,15 @@ // xcb #if HAVE_XCB_COMPOSITE #include -#endif +#if HAVE_EGL +#include +#include +#include // egl.h could include XLib.h + +#endif // HAVE_EGL + +#endif // HAVE_XCB_COMPOSITE class KWindowInfo; namespace Plasma { @@ -95,13 +102,24 @@ private: xcb_pixmap_t m_pixmap; uint m_texture; #if HAVE_GLX + bool windowToTextureGLX(WindowTextureNode *textureNode); void resolveGLXFunctions(); bool loadGLXTexture(); void bindGLXTexture(); QFunctionPointer m_bindTexImage; QFunctionPointer m_releaseTexImage; xcb_pixmap_t m_glxPixmap; -#endif +#endif // HAVE_GLX +#if HAVE_EGL + bool xcbWindowToTextureEGL(WindowTextureNode *textureNode); + void resolveEGLFunctions(); + void bindEGLTexture(); + bool m_eglFunctionsResolved; + QFunctionPointer m_eglCreateImageKHR; + QFunctionPointer m_eglDestroyImageKHR; + QFunctionPointer m_glEGLImageTargetTexture2DOES; + EGLImageKHR m_image; +#endif // HAVE_EGL #endif }; diff --git a/src/plasma/config-plasma.h.cmake b/src/plasma/config-plasma.h.cmake index c1aa75db7..1d59ae3ce 100644 --- a/src/plasma/config-plasma.h.cmake +++ b/src/plasma/config-plasma.h.cmake @@ -3,3 +3,4 @@ #cmakedefine01 PLASMA_NO_KUTILS #cmakedefine01 HAVE_X11 #cmakedefine01 HAVE_GLX +#cmakedefine01 HAVE_EGL