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
icc-effect-5.26.4
Vlad Zahorodnii 2020-10-16 17:57:35 +03:00
parent da12d3804f
commit 292335beac
6 changed files with 90 additions and 9 deletions

View File

@ -567,4 +567,14 @@ QString Platform::supportInformation() const
return QStringLiteral("Name: %1\n").arg(metaObject()->className());
}
EGLContext Platform::sceneEglGlobalShareContext() const
{
return m_globalShareContext;
}
void Platform::setSceneEglGlobalShareContext(EGLContext context)
{
m_globalShareContext = context;
}
}

View File

@ -118,6 +118,19 @@ public:
void setSceneEglContext(EGLContext context) {
m_context = context;
}
/**
* Returns the compositor-wide shared EGL context. This function may return EGL_NO_CONTEXT
* if the underlying rendering backend does not use EGL.
*
* Note that the returned context should never be made current. Instead, create a context
* that shares with this one and make the new context current.
*/
EGLContext sceneEglGlobalShareContext() const;
/**
* Sets the global share context to @a context. This function is intended to be called only
* by rendering backends.
*/
void setSceneEglGlobalShareContext(EGLContext context);
/**
* The first (in case of multiple) EGLSurface used by the compositing scene.
*/
@ -544,6 +557,7 @@ private:
EGLDisplay m_eglDisplay;
EGLConfig m_eglConfig = nullptr;
EGLContext m_context = EGL_NO_CONTEXT;
EGLContext m_globalShareContext = EGL_NO_CONTEXT;
EGLSurface m_surface = EGL_NO_SURFACE;
int m_hideCursorCounter = 0;
ColorCorrect::Manager *m_colorCorrect = nullptr;

View File

@ -40,11 +40,61 @@ eglBindWaylandDisplayWL_func eglBindWaylandDisplayWL = nullptr;
eglUnbindWaylandDisplayWL_func eglUnbindWaylandDisplayWL = nullptr;
eglQueryWaylandBufferWL_func eglQueryWaylandBufferWL = nullptr;
static EGLContext s_globalShareContext = EGL_NO_CONTEXT;
static bool isOpenGLES_helper()
{
if (qstrcmp(qgetenv("KWIN_COMPOSE"), "O2ES") == 0) {
return true;
}
return QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES;
}
static bool ensureGlobalShareContext()
{
const EGLDisplay eglDisplay = kwinApp()->platform()->sceneEglDisplay();
const EGLConfig eglConfig = kwinApp()->platform()->sceneEglConfig();
if (s_globalShareContext != EGL_NO_CONTEXT) {
return true;
}
std::vector<int> attribs;
if (isOpenGLES_helper()) {
EglOpenGLESContextAttributeBuilder builder;
builder.setVersion(2);
attribs = builder.build();
} else {
EglContextAttributeBuilder builder;
attribs = builder.build();
}
s_globalShareContext = eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, attribs.data());
if (s_globalShareContext == EGL_NO_CONTEXT) {
qCWarning(KWIN_OPENGL, "Failed to create global share context: 0x%x", eglGetError());
}
kwinApp()->platform()->setSceneEglGlobalShareContext(s_globalShareContext);
return s_globalShareContext != EGL_NO_CONTEXT;
}
static void destroyGlobalShareContext()
{
const EGLDisplay eglDisplay = kwinApp()->platform()->sceneEglDisplay();
if (eglDisplay == EGL_NO_DISPLAY || s_globalShareContext == EGL_NO_CONTEXT) {
return;
}
eglDestroyContext(eglDisplay, s_globalShareContext);
s_globalShareContext = EGL_NO_CONTEXT;
kwinApp()->platform()->setSceneEglGlobalShareContext(EGL_NO_CONTEXT);
}
AbstractEglBackend::AbstractEglBackend()
: QObject(nullptr)
, OpenGLBackend()
{
connect(Compositor::self(), &Compositor::aboutToDestroy, this, &AbstractEglBackend::unbindWaylandDisplay);
connect(Compositor::self(), &Compositor::aboutToDestroy, this, &AbstractEglBackend::teardown);
}
AbstractEglBackend::~AbstractEglBackend()
@ -52,11 +102,12 @@ AbstractEglBackend::~AbstractEglBackend()
delete m_dmaBuf;
}
void AbstractEglBackend::unbindWaylandDisplay()
void AbstractEglBackend::teardown()
{
if (eglUnbindWaylandDisplayWL && m_display != EGL_NO_DISPLAY) {
eglUnbindWaylandDisplayWL(m_display, *(WaylandServer::self()->display()));
}
destroyGlobalShareContext();
}
void AbstractEglBackend::cleanup()
@ -199,14 +250,15 @@ void AbstractEglBackend::doneCurrent()
bool AbstractEglBackend::isOpenGLES() const
{
if (qstrcmp(qgetenv("KWIN_COMPOSE"), "O2ES") == 0) {
return true;
}
return QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES;
return isOpenGLES_helper();
}
bool AbstractEglBackend::createContext()
{
if (!ensureGlobalShareContext()) {
return false;
}
const bool haveRobustness = hasExtension(QByteArrayLiteral("EGL_EXT_create_context_robustness"));
const bool haveCreateContext = hasExtension(QByteArrayLiteral("EGL_KHR_create_context"));
const bool haveContextPriority = hasExtension(QByteArrayLiteral("EGL_IMG_context_priority"));
@ -277,7 +329,7 @@ bool AbstractEglBackend::createContext()
EGLContext ctx = EGL_NO_CONTEXT;
for (auto it = candidates.begin(); it != candidates.end(); it++) {
const auto attribs = (*it)->build();
ctx = eglCreateContext(m_display, config(), EGL_NO_CONTEXT, attribs.data());
ctx = eglCreateContext(m_display, config(), s_globalShareContext, attribs.data());
if (ctx != EGL_NO_CONTEXT) {
qCDebug(KWIN_OPENGL) << "Created EGL context with attributes:" << (*it).get();
break;

View File

@ -69,7 +69,7 @@ protected:
bool createContext();
private:
void unbindWaylandDisplay();
void teardown();
EGLDisplay m_display = EGL_NO_DISPLAY;
EGLSurface m_surface = EGL_NO_SURFACE;

View File

@ -33,7 +33,7 @@ namespace QPA
EGLPlatformContext::EGLPlatformContext(QOpenGLContext *context, EGLDisplay display)
: m_eglDisplay(display)
{
create(context->format(), kwinApp()->platform()->sceneEglContext());
create(context->format(), kwinApp()->platform()->sceneEglGlobalShareContext());
}
EGLPlatformContext::~EGLPlatformContext()

View File

@ -10,6 +10,7 @@
#include "integration.h"
#include "backingstore.h"
#include "eglplatformcontext.h"
#include "logging.h"
#include "offscreensurface.h"
#include "screen.h"
#include "window.h"
@ -122,6 +123,10 @@ QStringList Integration::themeNames() const
QPlatformOpenGLContext *Integration::createPlatformOpenGLContext(QOpenGLContext *context) const
{
if (kwinApp()->platform()->sceneEglGlobalShareContext() == EGL_NO_CONTEXT) {
qCWarning(KWIN_QPA) << "Attempting to create a QOpenGLContext before the scene is initialized";
return nullptr;
}
const EGLDisplay eglDisplay = kwinApp()->platform()->sceneEglDisplay();
if (eglDisplay != EGL_NO_DISPLAY) {
EGLPlatformContext *platformContext = new EGLPlatformContext(context, eglDisplay);