kwin/plugins/qpa/eglplatformcontext.cpp
Vlad Zahorodnii 292335beac Introduce persistent global share context
On Wayland, internal windows that use OpenGL are rendered into fbos,
which are later handed over to kwin. In order to achieve that, our QPA
creates OpenGL contexts that share resources with the scene's context.

The problems start when compositing has been restarted. If user changes
any compositing settings, the underlying render backend will be
reinitialized and with it, the scene's context will be destroyed. Thus,
we no longer can accept framebuffer objects from internal windows.

This change addresses the framebuffer object sharing problem by adding
a so called global share context. It persists throughout the lifetime of
kwin. It can never be made current. The scene context and all contexts
created in our QPA share resources with it.

Therefore we can destroy the scene OpenGL context without affecting
OpenGL contexts owned by internal windows, e.g. the outline visual or
tabbox.

It's worth noting that Qt provides a way to create a global share
context. But for our purposes it's not suitable since the share
context must be known when QGuiApplication attempts to instantiate a
QOpenGLContext object. At that moment, the backend is not initialized
and thus the EGLDisplay is not available yet.

BUG: 415798
2020-10-19 12:13:15 +03:00

268 lines
9.6 KiB
C++

/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "eglplatformcontext.h"
#include "egl_context_attribute_builder.h"
#include "eglhelpers.h"
#include "internal_client.h"
#include "offscreensurface.h"
#include "platform.h"
#include "window.h"
#include "logging.h"
#include <QOpenGLContext>
#include <QOpenGLFramebufferObject>
#include <private/qopenglcontext_p.h>
#include <memory>
namespace KWin
{
namespace QPA
{
EGLPlatformContext::EGLPlatformContext(QOpenGLContext *context, EGLDisplay display)
: m_eglDisplay(display)
{
create(context->format(), kwinApp()->platform()->sceneEglGlobalShareContext());
}
EGLPlatformContext::~EGLPlatformContext()
{
if (m_context != EGL_NO_CONTEXT) {
eglDestroyContext(m_eglDisplay, m_context);
}
}
EGLDisplay EGLPlatformContext::eglDisplay() const
{
return m_eglDisplay;
}
EGLContext EGLPlatformContext::eglContext() const
{
return m_context;
}
static EGLSurface eglSurfaceForPlatformSurface(QPlatformSurface *surface)
{
if (surface->surface()->surfaceClass() == QSurface::Window) {
return static_cast<Window *>(surface)->eglSurface();
} else {
return static_cast<OffscreenSurface *>(surface)->eglSurface();
}
}
bool EGLPlatformContext::makeCurrent(QPlatformSurface *surface)
{
const EGLSurface eglSurface = eglSurfaceForPlatformSurface(surface);
const bool ok = eglMakeCurrent(eglDisplay(), eglSurface, eglSurface, eglContext());
if (!ok) {
qCWarning(KWIN_QPA, "eglMakeCurrent failed: %x", eglGetError());
return false;
}
if (surface->surface()->surfaceClass() == QSurface::Window) {
// QOpenGLContextPrivate::setCurrentContext will be called after this
// method returns, but that's too late, as we need a current context in
// order to bind the content framebuffer object.
QOpenGLContextPrivate::setCurrentContext(context());
Window *window = static_cast<Window *>(surface);
window->bindContentFBO();
}
return true;
}
void EGLPlatformContext::doneCurrent()
{
eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
}
bool EGLPlatformContext::isValid() const
{
return m_context != EGL_NO_CONTEXT;
}
bool EGLPlatformContext::isSharing() const
{
return false;
}
QSurfaceFormat EGLPlatformContext::format() const
{
return m_format;
}
QFunctionPointer EGLPlatformContext::getProcAddress(const char *procName)
{
return eglGetProcAddress(procName);
}
void EGLPlatformContext::swapBuffers(QPlatformSurface *surface)
{
if (surface->surface()->surfaceClass() == QSurface::Window) {
Window *window = static_cast<Window *>(surface);
InternalClient *client = window->client();
if (!client) {
return;
}
context()->makeCurrent(surface->surface());
glFlush();
client->present(window->swapFBO());
window->bindContentFBO();
}
}
GLuint EGLPlatformContext::defaultFramebufferObject(QPlatformSurface *surface) const
{
if (Window *window = dynamic_cast<Window *>(surface)) {
const auto &fbo = window->contentFBO();
if (!fbo.isNull()) {
return fbo->handle();
}
qCDebug(KWIN_QPA) << "No default framebuffer object for internal window";
}
return 0;
}
void EGLPlatformContext::create(const QSurfaceFormat &format, EGLContext shareContext)
{
if (!eglBindAPI(isOpenGLES() ? EGL_OPENGL_ES_API : EGL_OPENGL_API)) {
qCWarning(KWIN_QPA, "eglBindAPI failed: 0x%x", eglGetError());
return;
}
m_config = configFromFormat(m_eglDisplay, format);
if (m_config == EGL_NO_CONFIG_KHR) {
qCWarning(KWIN_QPA) << "Could not find suitable EGLConfig for" << format;
return;
}
m_format = formatFromConfig(m_eglDisplay, m_config);
const QByteArray eglExtensions = eglQueryString(eglDisplay(), EGL_EXTENSIONS);
const QList<QByteArray> extensions = eglExtensions.split(' ');
const bool haveRobustness = extensions.contains(QByteArrayLiteral("EGL_EXT_create_context_robustness"));
const bool haveCreateContext = extensions.contains(QByteArrayLiteral("EGL_KHR_create_context"));
const bool haveContextPriority = extensions.contains(QByteArrayLiteral("EGL_IMG_context_priority"));
std::vector<std::unique_ptr<AbstractOpenGLContextAttributeBuilder>> candidates;
if (isOpenGLES()) {
if (haveCreateContext && haveRobustness && haveContextPriority) {
auto glesRobustPriority = std::make_unique<EglOpenGLESContextAttributeBuilder>();
glesRobustPriority->setVersion(2);
glesRobustPriority->setRobust(true);
glesRobustPriority->setHighPriority(true);
candidates.push_back(std::move(glesRobustPriority));
}
if (haveCreateContext && haveRobustness) {
auto glesRobust = std::make_unique<EglOpenGLESContextAttributeBuilder>();
glesRobust->setVersion(2);
glesRobust->setRobust(true);
candidates.push_back(std::move(glesRobust));
}
if (haveContextPriority) {
auto glesPriority = std::make_unique<EglOpenGLESContextAttributeBuilder>();
glesPriority->setVersion(2);
glesPriority->setHighPriority(true);
candidates.push_back(std::move(glesPriority));
}
auto gles = std::make_unique<EglOpenGLESContextAttributeBuilder>();
gles->setVersion(2);
candidates.push_back(std::move(gles));
} else {
// Try to create a 3.1 core context
if (m_format.majorVersion() >= 3 && haveCreateContext) {
if (haveRobustness && haveContextPriority) {
auto robustCorePriority = std::make_unique<EglContextAttributeBuilder>();
robustCorePriority->setVersion(m_format.majorVersion(), m_format.minorVersion());
robustCorePriority->setRobust(true);
robustCorePriority->setForwardCompatible(true);
if (m_format.profile() == QSurfaceFormat::CoreProfile) {
robustCorePriority->setCoreProfile(true);
} else if (m_format.profile() == QSurfaceFormat::CompatibilityProfile) {
robustCorePriority->setCompatibilityProfile(true);
}
robustCorePriority->setHighPriority(true);
candidates.push_back(std::move(robustCorePriority));
}
if (haveRobustness) {
auto robustCore = std::make_unique<EglContextAttributeBuilder>();
robustCore->setVersion(m_format.majorVersion(), m_format.minorVersion());
robustCore->setRobust(true);
robustCore->setForwardCompatible(true);
if (m_format.profile() == QSurfaceFormat::CoreProfile) {
robustCore->setCoreProfile(true);
} else if (m_format.profile() == QSurfaceFormat::CompatibilityProfile) {
robustCore->setCompatibilityProfile(true);
}
candidates.push_back(std::move(robustCore));
}
if (haveContextPriority) {
auto corePriority = std::make_unique<EglContextAttributeBuilder>();
corePriority->setVersion(m_format.majorVersion(), m_format.minorVersion());
corePriority->setForwardCompatible(true);
if (m_format.profile() == QSurfaceFormat::CoreProfile) {
corePriority->setCoreProfile(true);
} else if (m_format.profile() == QSurfaceFormat::CompatibilityProfile) {
corePriority->setCompatibilityProfile(true);
}
corePriority->setHighPriority(true);
candidates.push_back(std::move(corePriority));
}
auto core = std::make_unique<EglContextAttributeBuilder>();
core->setVersion(m_format.majorVersion(), m_format.minorVersion());
core->setForwardCompatible(true);
if (m_format.profile() == QSurfaceFormat::CoreProfile) {
core->setCoreProfile(true);
} else if (m_format.profile() == QSurfaceFormat::CompatibilityProfile) {
core->setCompatibilityProfile(true);
}
candidates.push_back(std::move(core));
}
if (haveRobustness && haveCreateContext && haveContextPriority) {
auto robustPriority = std::make_unique<EglContextAttributeBuilder>();
robustPriority->setRobust(true);
robustPriority->setHighPriority(true);
candidates.push_back(std::move(robustPriority));
}
if (haveRobustness && haveCreateContext) {
auto robust = std::make_unique<EglContextAttributeBuilder>();
robust->setRobust(true);
candidates.push_back(std::move(robust));
}
candidates.emplace_back(new EglContextAttributeBuilder);
}
EGLContext context = EGL_NO_CONTEXT;
for (auto it = candidates.begin(); it != candidates.end(); it++) {
const auto attribs = (*it)->build();
context = eglCreateContext(eglDisplay(), m_config, shareContext, attribs.data());
if (context != EGL_NO_CONTEXT) {
qCDebug(KWIN_QPA) << "Created EGL context with attributes:" << (*it).get();
break;
}
}
if (context == EGL_NO_CONTEXT) {
qCWarning(KWIN_QPA) << "Failed to create EGL context";
return;
}
m_context = context;
}
} // namespace QPA
} // namespace KWin