Commit ef6fa25d authored by Vlad Zahorodnii's avatar Vlad Zahorodnii

platforms/x11: Split out the EGL for X11 backend

EGL for X and EGL for Wayland backends are quite different. The main
motivation behind this change is to prepare the EGL backends for
monitoring vblank events. Things work quite differently depending on
if the EGL backend renders onto a toplevel window or overlay window.
parent 4f3adbb5
......@@ -12,19 +12,10 @@
#include "options.h"
#include "overlaywindow.h"
#include "platform.h"
#include "scene.h"
#include "screens.h"
#include "xcbutils.h"
#include "texture.h"
// kwin libs
#include <kwinglplatform.h>
#include <kwinglutils.h>
// Qt
#include <QLoggingCategory>
#include <QDebug>
#include <QOpenGLContext>
// system
#include <unistd.h>
Q_LOGGING_CATEGORY(KWIN_CORE, "kwin_core", QtCriticalMsg)
......@@ -35,7 +26,6 @@ EglOnXBackend::EglOnXBackend(Display *display)
: AbstractEglBackend()
, m_overlayWindow(kwinApp()->platform()->createOverlayWindow())
, surfaceHasSubPost(0)
, m_bufferAge(0)
, m_usesOverlayWindow(true)
, m_connection(connection())
, m_x11Display(display)
......@@ -50,7 +40,6 @@ EglOnXBackend::EglOnXBackend(xcb_connection_t *connection, Display *display, xcb
: AbstractEglBackend()
, m_overlayWindow(nullptr)
, surfaceHasSubPost(0)
, m_bufferAge(0)
, m_usesOverlayWindow(false)
, m_connection(connection)
, m_x11Display(display)
......@@ -139,8 +128,6 @@ void EglOnXBackend::init()
qCWarning(KWIN_CORE) << "eglPostSubBufferNV not supported, have to enable buffer preservation - which breaks v-sync and performance";
eglSurfaceAttrib(eglDisplay(), surface(), EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED);
}
initWayland();
}
bool EglOnXBackend::initRenderingContext()
......@@ -295,84 +282,6 @@ bool EglOnXBackend::initBufferConfigs()
return true;
}
void EglOnXBackend::presentSurface(EGLSurface surface, const QRegion &damage, const QRect &screenGeometry)
{
if (damage.isEmpty()) {
return;
}
const bool fullRepaint = supportsBufferAge() || (damage == screenGeometry);
if (fullRepaint || !surfaceHasSubPost) {
// the entire screen changed, or we cannot do partial updates (which implies we enabled surface preservation)
eglSwapBuffers(eglDisplay(), surface);
if (supportsBufferAge()) {
eglQuerySurface(eglDisplay(), surface, EGL_BUFFER_AGE_EXT, &m_bufferAge);
}
} else {
// a part of the screen changed, and we can use eglPostSubBufferNV to copy the updated area
for (const QRect &r : damage) {
eglPostSubBufferNV(eglDisplay(), surface, r.left(), screenGeometry.height() - r.bottom() - 1, r.width(), r.height());
}
}
}
void EglOnXBackend::screenGeometryChanged(const QSize &size)
{
Q_UNUSED(size)
// TODO: base implementation in OpenGLBackend
// The back buffer contents are now undefined
m_bufferAge = 0;
}
SceneOpenGLTexturePrivate *EglOnXBackend::createBackendTexture(SceneOpenGLTexture *texture)
{
return new EglTexture(texture, this);
}
QRegion EglOnXBackend::beginFrame(int screenId)
{
Q_UNUSED(screenId)
QRegion repaint;
if (supportsBufferAge())
repaint = accumulatedDamageHistory(m_bufferAge);
eglWaitNative(EGL_CORE_NATIVE_ENGINE);
return repaint;
}
void EglOnXBackend::endFrame(int screenId, const QRegion &renderedRegion, const QRegion &damagedRegion)
{
Q_UNUSED(screenId)
if (damagedRegion.isEmpty()) {
// 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;
}
presentSurface(surface(), renderedRegion, screens()->geometry());
if (m_overlayWindow && overlayWindow()->window()) // show the window only after the first pass,
overlayWindow()->show(); // since that pass may take long
// Save the damaged region to history
if (supportsBufferAge())
addToDamageHistory(damagedRegion);
}
bool EglOnXBackend::usesOverlayWindow() const
{
return m_usesOverlayWindow;
......@@ -388,72 +297,4 @@ bool EglOnXBackend::makeContextCurrent(const EGLSurface &surface)
return eglMakeCurrent(eglDisplay(), surface, surface, context()) == EGL_TRUE;
}
/************************************************
* EglTexture
************************************************/
EglTexture::EglTexture(KWin::SceneOpenGLTexture *texture, KWin::EglOnXBackend *backend)
: AbstractEglTexture(texture, backend)
, m_backend(backend)
{
}
EglTexture::~EglTexture() = default;
bool EglTexture::loadTexture(WindowPixmap *pixmap)
{
// first try the Wayland enabled loading
if (AbstractEglTexture::loadTexture(pixmap)) {
return true;
}
// did not succeed, try on X11
return loadTexture(pixmap->pixmap(), pixmap->toplevel()->bufferGeometry().size());
}
bool EglTexture::loadTexture(xcb_pixmap_t pix, const QSize &size)
{
if (!m_backend->isX11TextureFromPixmapSupported()) {
return false;
}
if (pix == XCB_NONE)
return false;
glGenTextures(1, &m_texture);
auto q = texture();
q->setWrapMode(GL_CLAMP_TO_EDGE);
q->setFilter(GL_LINEAR);
q->bind();
const EGLint attribs[] = {
EGL_IMAGE_PRESERVED_KHR, EGL_TRUE,
EGL_NONE
};
setImage(eglCreateImageKHR(m_backend->eglDisplay(), EGL_NO_CONTEXT, EGL_NATIVE_PIXMAP_KHR,
(EGLClientBuffer)pix, attribs));
if (EGL_NO_IMAGE_KHR == image()) {
qCDebug(KWIN_CORE) << "failed to create egl image";
q->unbind();
q->discard();
return false;
}
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)image());
q->unbind();
q->setYInverted(true);
m_size = size;
updateMatrix();
return true;
}
void KWin::EglTexture::onDamage()
{
if (options->isGlStrictBinding()) {
// This is just implemented to be consistent with
// the example in mesa/demos/src/egl/opengles1/texture_from_pixmap.c
eglWaitNative(EGL_CORE_NATIVE_ENGINE);
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES) image());
}
GLTexturePrivate::onDamage();
}
} // namespace
......@@ -27,20 +27,11 @@ public:
EglOnXBackend(Display *display);
explicit EglOnXBackend(xcb_connection_t *connection, Display *display, xcb_window_t rootWindow, int screenNumber, xcb_window_t renderingWindow);
~EglOnXBackend() override;
void screenGeometryChanged(const QSize &size) override;
SceneOpenGLTexturePrivate *createBackendTexture(SceneOpenGLTexture *texture) override;
QRegion beginFrame(int screenId) override;
void endFrame(int screenId, const QRegion &damage, const QRegion &damagedRegion) override;
OverlayWindow* overlayWindow() const override;
bool usesOverlayWindow() const override;
void init() override;
bool isX11TextureFromPixmapSupported() const {
return m_x11TextureFromPixmapSupported;
}
protected:
void presentSurface(EGLSurface surface, const QRegion &damage, const QRect &screenGeometry);
virtual bool createSurfaces();
EGLSurface createSurface(xcb_window_t window);
void setHavePlatformBase(bool have) {
......@@ -49,11 +40,10 @@ protected:
bool havePlatformBase() const {
return m_havePlatformBase;
}
bool makeContextCurrent(const EGLSurface &surface);
void setX11TextureFromPixmapSupported(bool set) {
m_x11TextureFromPixmapSupported = set;
bool havePostSubBuffer() const {
return surfaceHasSubPost;
}
bool makeContextCurrent(const EGLSurface &surface);
private:
bool initBufferConfigs();
......@@ -63,7 +53,6 @@ private:
*/
OverlayWindow *m_overlayWindow;
int surfaceHasSubPost;
int m_bufferAge;
bool m_usesOverlayWindow;
xcb_connection_t *m_connection;
Display *m_x11Display;
......@@ -71,25 +60,6 @@ private:
int m_x11ScreenNumber;
xcb_window_t m_renderingWindow = XCB_WINDOW_NONE;
bool m_havePlatformBase = false;
bool m_x11TextureFromPixmapSupported = true;
friend class EglTexture;
};
/**
* @brief Texture using an EGLImageKHR.
*/
class EglTexture : public AbstractEglTexture
{
public:
~EglTexture() override;
void onDamage() override;
bool loadTexture(WindowPixmap *pixmap) override;
private:
bool loadTexture(xcb_pixmap_t pix, const QSize &size);
friend class EglOnXBackend;
EglTexture(SceneOpenGLTexture *texture, EglOnXBackend *backend);
EglOnXBackend *m_backend;
};
} // namespace
......
......@@ -2,6 +2,7 @@ set(X11PLATFORM_SOURCES
edge.cpp
effects_mouse_interception_x11_filter.cpp
effects_x11.cpp
eglbackend.cpp
logging.cpp
non_composited_outline.cpp
overlaywindow_x11.cpp
......
/*
SPDX-FileCopyrightText: 2010, 2012 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "eglbackend.h"
#include "options.h"
#include "overlaywindow.h"
#include "scene.h"
#include "screens.h"
namespace KWin
{
EglBackend::EglBackend(Display *display)
: EglOnXBackend(display)
{
}
SceneOpenGLTexturePrivate *EglBackend::createBackendTexture(SceneOpenGLTexture *texture)
{
return new EglTexture(texture, this);
}
void EglBackend::screenGeometryChanged(const QSize &size)
{
Q_UNUSED(size)
// TODO: base implementation in OpenGLBackend
// The back buffer contents are now undefined
m_bufferAge = 0;
}
QRegion EglBackend::beginFrame(int screenId)
{
Q_UNUSED(screenId)
QRegion repaint;
if (supportsBufferAge())
repaint = accumulatedDamageHistory(m_bufferAge);
eglWaitNative(EGL_CORE_NATIVE_ENGINE);
return repaint;
}
void EglBackend::endFrame(int screenId, const QRegion &renderedRegion, const QRegion &damagedRegion)
{
Q_UNUSED(screenId)
if (damagedRegion.isEmpty()) {
// 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;
}
presentSurface(surface(), renderedRegion, screens()->geometry());
if (overlayWindow() && overlayWindow()->window()) { // show the window only after the first pass,
overlayWindow()->show(); // since that pass may take long
}
// Save the damaged region to history
if (supportsBufferAge()) {
addToDamageHistory(damagedRegion);
}
}
void EglBackend::presentSurface(EGLSurface surface, const QRegion &damage, const QRect &screenGeometry)
{
if (damage.isEmpty()) {
return;
}
const bool fullRepaint = supportsBufferAge() || (damage == screenGeometry);
if (fullRepaint || !havePostSubBuffer()) {
// the entire screen changed, or we cannot do partial updates (which implies we enabled surface preservation)
eglSwapBuffers(eglDisplay(), surface);
if (supportsBufferAge()) {
eglQuerySurface(eglDisplay(), surface, EGL_BUFFER_AGE_EXT, &m_bufferAge);
}
} else {
// a part of the screen changed, and we can use eglPostSubBufferNV to copy the updated area
for (const QRect &r : damage) {
eglPostSubBufferNV(eglDisplay(), surface, r.left(), screenGeometry.height() - r.bottom() - 1, r.width(), r.height());
}
}
}
/************************************************
* EglTexture
************************************************/
EglTexture::EglTexture(KWin::SceneOpenGLTexture *texture, EglBackend *backend)
: AbstractEglTexture(texture, backend)
, m_backend(backend)
{
}
EglTexture::~EglTexture()
{
}
bool EglTexture::loadTexture(WindowPixmap *pixmap)
{
const xcb_pixmap_t nativePixmap = pixmap->pixmap();
if (nativePixmap == XCB_NONE) {
return false;
}
glGenTextures(1, &m_texture);
auto q = texture();
q->setWrapMode(GL_CLAMP_TO_EDGE);
q->setFilter(GL_LINEAR);
q->bind();
const EGLint attribs[] = {
EGL_IMAGE_PRESERVED_KHR, EGL_TRUE,
EGL_NONE
};
setImage(eglCreateImageKHR(m_backend->eglDisplay(), EGL_NO_CONTEXT, EGL_NATIVE_PIXMAP_KHR,
(EGLClientBuffer)nativePixmap, attribs));
if (EGL_NO_IMAGE_KHR == image()) {
qCDebug(KWIN_CORE) << "failed to create egl image";
q->unbind();
q->discard();
return false;
}
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)image());
q->unbind();
q->setYInverted(true);
m_size = pixmap->toplevel()->bufferGeometry().size();
updateMatrix();
return true;
}
void EglTexture::onDamage()
{
if (options->isGlStrictBinding()) {
// This is just implemented to be consistent with
// the example in mesa/demos/src/egl/opengles1/texture_from_pixmap.c
eglWaitNative(EGL_CORE_NATIVE_ENGINE);
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES) image());
}
GLTexturePrivate::onDamage();
}
} // namespace KWin
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "eglonxbackend.h"
namespace KWin
{
class EglBackend : public EglOnXBackend
{
Q_OBJECT
public:
explicit EglBackend(Display *display);
SceneOpenGLTexturePrivate *createBackendTexture(SceneOpenGLTexture *texture) override;
QRegion beginFrame(int screenId) override;
void endFrame(int screenId, const QRegion &damage, const QRegion &damagedRegion) override;
void screenGeometryChanged(const QSize &size) override;
private:
void presentSurface(EGLSurface surface, const QRegion &damage, const QRect &screenGeometry);
int m_bufferAge = 0;
};
class EglTexture : public AbstractEglTexture
{
public:
~EglTexture() override;
void onDamage() override;
bool loadTexture(WindowPixmap *pixmap) override;
private:
friend class EglBackend;
EglTexture(SceneOpenGLTexture *texture, EglBackend *backend);
EglBackend *m_backend;
};
} // namespace KWin
......@@ -25,7 +25,7 @@
#include "abstract_client.h"
#include "composite.h"
#include "effects_x11.h"
#include "eglonxbackend.h"
#include "eglbackend.h"
#include "keyboard_input.h"
#include "logging.h"
#include "screenedges_filter.h"
......@@ -160,7 +160,7 @@ OpenGLBackend *X11StandalonePlatform::createOpenGLBackend()
}
#endif
case EglPlatformInterface:
return new EglOnXBackend(m_x11Display);
return new EglBackend(m_x11Display);
default:
// no backend available
return nullptr;
......
......@@ -20,11 +20,19 @@ EglX11Backend::EglX11Backend(X11WindowedBackend *backend)
: EglOnXBackend(backend->connection(), backend->display(), backend->rootWindow(), backend->screenNumer(), XCB_WINDOW_NONE)
, m_backend(backend)
{
setX11TextureFromPixmapSupported(false);
}
EglX11Backend::~EglX11Backend() = default;
void EglX11Backend::init()
{
EglOnXBackend::init();
if (!isFailed()) {
initWayland();
}
}
void EglX11Backend::cleanupSurfaces()
{
for (auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
......@@ -78,4 +86,45 @@ void EglX11Backend::endFrame(int screenId, const QRegion &renderedRegion, const
presentSurface(m_surfaces.at(screenId), renderedRegion, outputGeometry);
}
void EglX11Backend::presentSurface(EGLSurface surface, const QRegion &damage, const QRect &screenGeometry)
{
if (damage.isEmpty()) {
return;
}
const bool fullRepaint = supportsBufferAge() || (damage == screenGeometry);
if (fullRepaint || !havePostSubBuffer()) {
// the entire screen changed, or we cannot do partial updates (which implies we enabled surface preservation)
eglSwapBuffers(eglDisplay(), surface);
} else {
// a part of the screen changed, and we can use eglPostSubBufferNV to copy the updated area
for (const QRect &r : damage) {
eglPostSubBufferNV(eglDisplay(), surface, r.left(), screenGeometry.height() - r.bottom() - 1, r.width(), r.height());
}
}
}
SceneOpenGLTexturePrivate *EglX11Backend::createBackendTexture(SceneOpenGLTexture *texture)
{
return new EglX11Texture(texture, this);
}
void EglX11Backend::screenGeometryChanged(const QSize &size)
{
Q_UNUSED(size)
}
/************************************************
* EglX11Texture
************************************************/
EglX11Texture::EglX11Texture(KWin::SceneOpenGLTexture *texture, EglX11Backend *backend)
: AbstractEglTexture(texture, backend)
{
}
EglX11Texture::~EglX11Texture()
{
}
} // namespace
......@@ -23,9 +23,13 @@ class EglX11Backend : public EglOnXBackend
public:
explicit EglX11Backend(X11WindowedBackend *backend);
~EglX11Backend() override;
SceneOpenGLTexturePrivate *createBackendTexture(SceneOpenGLTexture *texture) override;
void init() override;
bool usesOverlayWindow() const override;
QRegion beginFrame(int screenId) override;
void endFrame(int screenId, const QRegion &damage, const QRegion &damagedRegion) override;
void screenGeometryChanged(const QSize &size) override;
protected:
void cleanupSurfaces() override;
......@@ -33,10 +37,25 @@ protected:
private:
void setupViewport(int screenId);
void presentSurface(EGLSurface surface, const QRegion &damage, const QRect &screenGeometry);
QVector<EGLSurface> m_surfaces;
X11WindowedBackend *m_backend;
};
/**
* @brief Texture using an EGLImageKHR.
*/
class EglX11Texture : public AbstractEglTexture
{
public:
~EglX11Texture() override;
private:
friend class EglX11Backend;
EglX11Texture(SceneOpenGLTexture *texture, EglX11Backend *backend);
};
} // namespace