Commit 26b3569a authored by Martin Flöser's avatar Martin Flöser
Browse files

[wayland] Add a QPA plugin for kwin_wayland

This introduces an own QPA plugin for KWin. QtWayland's plugin is not
a good solution for KWin as QtWayland is meant for Wayland clients and
not for a Wayland server. Given that it makes more sense to have a very
minimal QPA plugin which supports the use cases we actually have.

With our own QPA plugin we should be able to improve the following
areas:
* no need to create Wayland server before QApplication
* Qt::BypassWindowManagerHint can be supported
* no workaround for creating OpenGL context in main thread
* sharing OpenGL context with Qt
* OpenGL context for Qt on libhybris backend

The plugin supports so far the following features:
* creating a QPlatformWindow using KWayland::Client (ShellSurface)
* creating a QPlatformBackingStore using a ShmPool
* creating a QPlatformOpenGLContext with Wayland::EGL
* or creating a QPlatformOpenGLContext which shares with KWin's scene
* creating a QPlatformScreen for each KWayland::Client::Output
* QPlatformNativeInterface compatible to QtWayland
parent 79317717
......@@ -211,6 +211,21 @@ set_package_properties(X11_XCB PROPERTIES
PURPOSE "Required for building X11 windowed backend of kwin_wayland"
TYPE OPTIONAL)
# dependencies for QPA plugin
find_package(Qt5PlatformSupport REQUIRED)
find_package(Freetype REQUIRED)
set_package_properties(Freetype PROPERTIES DESCRIPTION "A font rendering engine"
URL "http://www.freetype.org"
TYPE REQUIRED
PURPOSE "Needed for KWin's QPA plugin."
)
find_package(Fontconfig REQUIRED)
set_package_properties(Fontconfig PROPERTIES DESCRIPTION "Font access configuration library"
URL "http://www.freedesktop.org/wiki/Software/fontconfig"
TYPE REQUIRED
PURPOSE "Needed for KWin's QPA plugin."
)
########### configure tests ###############
include(CMakeDependentOption)
......
# - Try to find the Fontconfig
# Once done this will define
#
# FONTCONFIG_FOUND - system has Fontconfig
# FONTCONFIG_INCLUDE_DIR - The include directory to use for the fontconfig headers
# FONTCONFIG_LIBRARIES - Link these to use FONTCONFIG
# FONTCONFIG_DEFINITIONS - Compiler switches required for using FONTCONFIG
# Copyright (c) 2006,2007 Laurent Montel, <montel@kde.org>
#
# Redistribution and use is allowed according to the terms of the BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
if (FONTCONFIG_LIBRARIES AND FONTCONFIG_INCLUDE_DIR)
# in cache already
set(FONTCONFIG_FOUND TRUE)
else (FONTCONFIG_LIBRARIES AND FONTCONFIG_INCLUDE_DIR)
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(PC_FONTCONFIG QUIET fontconfig)
set(FONTCONFIG_DEFINITIONS ${PC_FONTCONFIG_CFLAGS_OTHER})
endif (NOT WIN32)
find_path(FONTCONFIG_INCLUDE_DIR fontconfig/fontconfig.h
PATHS
${PC_FONTCONFIG_INCLUDEDIR}
${PC_FONTCONFIG_INCLUDE_DIRS}
/usr/X11/include
)
find_library(FONTCONFIG_LIBRARIES NAMES fontconfig
PATHS
${PC_FONTCONFIG_LIBDIR}
${PC_FONTCONFIG_LIBRARY_DIRS}
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Fontconfig DEFAULT_MSG FONTCONFIG_LIBRARIES FONTCONFIG_INCLUDE_DIR )
mark_as_advanced(FONTCONFIG_LIBRARIES FONTCONFIG_INCLUDE_DIR)
endif (FONTCONFIG_LIBRARIES AND FONTCONFIG_INCLUDE_DIR)
#.rst:
# FindQt5PlatformSupport
# -------
#
# Try to find Qt5PlatformSupport on a Unix system.
#
# This will define the following variables:
#
# ``Qt5PlatformSupport_FOUND``
# True if (the requested version of) Qt5PlatformSupport is available
# ``Qt5PlatformSupport_VERSION``
# The version of Qt5PlatformSupport
# ``Qt5PlatformSupport_LIBRARIES``
# This can be passed to target_link_libraries() instead of the ``Qt5PlatformSupport::Qt5PlatformSupport``
# target
# ``Qt5PlatformSupport_INCLUDE_DIRS``
# This should be passed to target_include_directories() if the target is not
# used for linking
# ``Qt5PlatformSupport_DEFINITIONS``
# This should be passed to target_compile_options() if the target is not
# used for linking
#
# If ``Qt5PlatformSupport_FOUND`` is TRUE, it will also define the following imported target:
#
# ``Qt5PlatformSupport::Qt5PlatformSupport``
# The Qt5PlatformSupport 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 <alex.merry@kde.org>
# Copyright 2014 Martin Gräßlin <mgraesslin@kde.org>
#
# 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 FindQt5PlatformSupport.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 FindQt5PlatformSupport.cmake")
endif()
# 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_Qt5PlatformSupport QUIET Qt5PlatformSupport)
set(Qt5PlatformSupport_DEFINITIONS ${PKG_Qt5PlatformSupport_CFLAGS_OTHER})
set(Qt5PlatformSupport_VERSION ${PKG_Qt5PlatformSupport_VERSION})
find_path(Qt5PlatformSupport_INCLUDE_DIR
NAMES
QtPlatformSupport/private/qfontconfigdatabase_p.h
HINTS
${PKG_Qt5PlatformSupport_INCLUDE_DIRS}/QtPlatformSupport/${PKG_Qt5PlatformSupport_VERSION}/
)
find_library(Qt5PlatformSupport_LIBRARY
NAMES
Qt5PlatformSupport
HINTS
${PKG_Qt5PlatformSupport_LIBRARY_DIRS}
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Qt5PlatformSupport
FOUND_VAR
Qt5PlatformSupport_FOUND
REQUIRED_VARS
Qt5PlatformSupport_LIBRARY
Qt5PlatformSupport_INCLUDE_DIR
VERSION_VAR
Qt5PlatformSupport_VERSION
)
if(Qt5PlatformSupport_FOUND AND NOT TARGET Qt5PlatformSupport::Qt5PlatformSupport)
add_library(Qt5PlatformSupport::Qt5PlatformSupport UNKNOWN IMPORTED)
set_target_properties(Qt5PlatformSupport::Qt5PlatformSupport PROPERTIES
IMPORTED_LOCATION "${Qt5PlatformSupport_LIBRARY}"
INTERFACE_COMPILE_OPTIONS "${Qt5PlatformSupport_DEFINITIONS}"
INTERFACE_INCLUDE_DIRECTORIES "${Qt5PlatformSupport_INCLUDE_DIR}"
)
endif()
mark_as_advanced(Qt5PlatformSupport_LIBRARY Qt5PlatformSupport_INCLUDE_DIR)
# compatibility variables
set(Qt5PlatformSupport_LIBRARIES ${Qt5PlatformSupport_LIBRARY})
set(Qt5PlatformSupport_INCLUDE_DIRS ${Qt5PlatformSupport_INCLUDE_DIR})
set(Qt5PlatformSupport_VERSION_STRING ${Qt5PlatformSupport_VERSION})
include(FeatureSummary)
set_package_properties(Qt5PlatformSupport PROPERTIES
URL "http://www.qt.io"
DESCRIPTION "Qt PlatformSupport module."
)
add_subdirectory(kglobalaccel)
add_subdirectory(qpa)
include_directories(${Qt5Core_PRIVATE_INCLUDE_DIRS})
include_directories(${Qt5Gui_PRIVATE_INCLUDE_DIRS})
add_definitions(-DQ_FONTCONFIGDATABASE)
set(QPA_SOURCES
abstractplatformcontext.cpp
backingstore.cpp
integration.cpp
main.cpp
nativeinterface.cpp
platformcontextwayland.cpp
screen.cpp
sharingplatformcontext.cpp
window.cpp
)
add_library(KWinQpaPlugin MODULE ${QPA_SOURCES})
target_link_libraries(KWinQpaPlugin
kwin
KF5::WaylandClient
Wayland::Egl
Qt5PlatformSupport::Qt5PlatformSupport
${FONTCONFIG_LIBRARIES}
${FREETYPE_LIBRARIES}
)
install(
TARGETS
KWinQpaPlugin
DESTINATION
${PLUGIN_INSTALL_DIR}/platforms/
)
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2015 Martin Gräßlin <mgraesslin@kde.org>
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 <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "abstractplatformcontext.h"
#include "integration.h"
namespace KWin
{
namespace QPA
{
static EGLConfig configFromGLFormat(EGLDisplay dpy, const QSurfaceFormat &format)
{
#define SIZE( __buffer__ ) format.__buffer__##BufferSize() > 0 ? format.__buffer__##BufferSize() : 0
// not setting samples as QtQuick doesn't need it
const EGLint config_attribs[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RED_SIZE, SIZE(red),
EGL_GREEN_SIZE, SIZE(green),
EGL_BLUE_SIZE, SIZE(blue),
EGL_ALPHA_SIZE, SIZE(alpha),
EGL_DEPTH_SIZE, SIZE(depth),
EGL_STENCIL_SIZE, SIZE(stencil),
#ifdef KWIN_HAVE_OPENGLES
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
#else
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
#endif
EGL_NONE,
};
#undef SIZE
EGLint count;
EGLConfig configs[1024];
if (eglChooseConfig(dpy, config_attribs, configs, 1, &count) == EGL_FALSE) {
return 0;
}
if (count != 1) {
return 0;
}
return configs[0];
}
static QSurfaceFormat formatFromConfig(EGLDisplay dpy, EGLConfig config)
{
QSurfaceFormat format;
EGLint value = 0;
#define HELPER(__egl__, __qt__) \
eglGetConfigAttrib(dpy, config, EGL_##__egl__, &value); \
format.set##__qt__(value); \
value = 0;
#define BUFFER_HELPER(__eglColor__, __color__) \
HELPER(__eglColor__##_SIZE, __color__##BufferSize)
BUFFER_HELPER(RED, Red)
BUFFER_HELPER(GREEN, Green)
BUFFER_HELPER(BLUE, Blue)
BUFFER_HELPER(ALPHA, Alpha)
BUFFER_HELPER(STENCIL, Stencil)
BUFFER_HELPER(DEPTH, Depth)
#undef BUFFER_HELPER
HELPER(SAMPLES, Samples)
#undef HELPER
#ifdef KWIN_HAVE_OPENGLES
format.setRenderableType(QSurfaceFormat::OpenGLES);
#else
format.setRenderableType(QSurfaceFormat::OpenGL);
#endif
format.setStereo(false);
return format;
}
AbstractPlatformContext::AbstractPlatformContext(QOpenGLContext *context, Integration *integration, EGLDisplay display)
: QPlatformOpenGLContext()
, m_integration(integration)
, m_eglDisplay(display)
, m_config(configFromGLFormat(m_eglDisplay, context->format()))
, m_format(formatFromConfig(m_eglDisplay, m_config))
{
}
AbstractPlatformContext::~AbstractPlatformContext()
{
if (m_context != EGL_NO_CONTEXT) {
eglDestroyContext(m_eglDisplay, m_context);
}
}
void AbstractPlatformContext::doneCurrent()
{
eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
}
QSurfaceFormat AbstractPlatformContext::format() const
{
return m_format;
}
QFunctionPointer AbstractPlatformContext::getProcAddress(const QByteArray &procName)
{
return eglGetProcAddress(procName.constData());
}
bool AbstractPlatformContext::isValid() const
{
return m_context != EGL_NO_CONTEXT;
}
bool AbstractPlatformContext::bindApi()
{
#ifdef KWIN_HAVE_OPENGLES
if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE) {
return false;
}
#else
if (eglBindAPI(EGL_OPENGL_API) == EGL_FALSE) {
return false;
}
#endif
return true;
}
void AbstractPlatformContext::createContext(EGLContext shareContext)
{
EGLContext context = EGL_NO_CONTEXT;
#ifdef KWIN_HAVE_OPENGLES
const EGLint context_attribs[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
context = eglCreateContext(eglDisplay(), config(), shareContext, context_attribs);
#else
const EGLint context_attribs_31_core[] = {
EGL_CONTEXT_MAJOR_VERSION_KHR, m_format.majorVersion(),
EGL_CONTEXT_MINOR_VERSION_KHR, m_format.minorVersion(),
EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR,
EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, m_format.profile() == QSurfaceFormat::CoreProfile ? EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR : EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR,
EGL_NONE
};
const EGLint context_attribs_legacy[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
const char* eglExtensionsCString = eglQueryString(eglDisplay(), EGL_EXTENSIONS);
const QList<QByteArray> extensions = QByteArray::fromRawData(eglExtensionsCString, qstrlen(eglExtensionsCString)).split(' ');
// Try to create a 3.1 core context
if (m_format.majorVersion() >= 3 && extensions.contains(QByteArrayLiteral("EGL_KHR_create_context"))) {
context = eglCreateContext(eglDisplay(), config(), shareContext, context_attribs_31_core);
}
if (context == EGL_NO_CONTEXT) {
context = eglCreateContext(eglDisplay(), config(), shareContext, context_attribs_legacy);
}
#endif
if (context == EGL_NO_CONTEXT) {
return;
}
m_context = context;
}
}
}
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2015 Martin Gräßlin <mgraesslin@kde.org>
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 <http://www.gnu.org/licenses/>.
*********************************************************************/
#ifndef KWIN_QPA_ABSTRACTPLATFORMCONTEXT_H
#define KWIN_QPA_ABSTRACTPLATFORMCONTEXT_H
#include <epoxy/egl.h>
#include <fixx11h.h>
#include <qpa/qplatformopenglcontext.h>
namespace KWin
{
namespace QPA
{
class Integration;
class AbstractPlatformContext : public QPlatformOpenGLContext
{
public:
explicit AbstractPlatformContext(QOpenGLContext *context, Integration *integration, EGLDisplay display);
virtual ~AbstractPlatformContext();
void doneCurrent() override;
QSurfaceFormat format() const override;
bool isValid() const override;
QFunctionPointer getProcAddress(const QByteArray &procName) override;
protected:
EGLDisplay eglDisplay() const {
return m_eglDisplay;
}
EGLConfig config() const {
return m_config;
}
bool bindApi();
EGLContext context() const {
return m_context;
}
void createContext(EGLContext shareContext = EGL_NO_CONTEXT);
private:
Integration *m_integration;
EGLDisplay m_eglDisplay;
EGLConfig m_config;
EGLContext m_context = EGL_NO_CONTEXT;
QSurfaceFormat m_format;
};
}
}
#endif
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2015 Martin Gräßlin <mgraesslin@kde.org>
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 <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "window.h"
#include "backingstore.h"
#include "../../wayland_server.h"
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/buffer.h>
#include <KWayland/Client/shm_pool.h>
#include <KWayland/Client/surface.h>
namespace KWin
{
namespace QPA
{
BackingStore::BackingStore(QWindow *w, KWayland::Client::ShmPool *shm)
: QPlatformBackingStore(w)
, m_shm(shm)
, m_backBuffer(QSize(), QImage::Format_ARGB32_Premultiplied)
{
QObject::connect(m_shm, &KWayland::Client::ShmPool::poolResized,
[this] {
if (!m_buffer) {
return;
}
auto b = m_buffer.toStrongRef();
if (!b->isUsed()){
return;
}
const QSize size = m_backBuffer.size();
m_backBuffer = QImage(b->address(), size.width(), size.height(), QImage::Format_ARGB32_Premultiplied);
}
);
}
BackingStore::~BackingStore() = default;
QPaintDevice *BackingStore::paintDevice()
{
return &m_backBuffer;
}
void BackingStore::resize(const QSize &size, const QRegion &staticContents)
{
Q_UNUSED(staticContents)
m_size = size;
if (!m_buffer) {
return;
}
m_buffer.toStrongRef()->setUsed(false);
m_buffer.clear();
}
void BackingStore::flush(QWindow *window, const QRegion &region, const QPoint &offset)
{
Q_UNUSED(region)
Q_UNUSED(offset)
auto s = static_cast<Window *>(window->handle())->surface();
s->attachBuffer(m_buffer);
// TODO: proper damage region
s->damage(QRect(QPoint(0, 0), m_backBuffer.size()));
s->commit(KWayland::Client::Surface::CommitFlag::None);
waylandServer()->internalClientConection()->flush();
waylandServer()->dispatch();
}
void BackingStore::beginPaint(const QRegion&)
{
if (m_buffer) {
auto b = m_buffer.toStrongRef();
if (b->isReleased()) {
// we can re-use this buffer
b->setReleased(false);
return;
} else {
// buffer is still in use, get a new one
b->setUsed(false);
}
}
auto oldBuffer = m_buffer.toStrongRef();
m_buffer.clear();
m_buffer = m_shm->getBuffer(m_size, m_size.width() * 4);
if (!m_buffer) {
m_backBuffer = QImage();
return;
}
auto b = m_buffer.toStrongRef();
b->setUsed(true);
m_backBuffer = QImage(b->address(), m_size.width(), m_size.height(), QImage::Format_ARGB32_Premultiplied);
if (oldBuffer) {
b->copy(oldBuffer->address());
} else {
m_backBuffer.fill(Qt::transparent);
}
}
}
}