File indexing completed on 2025-04-27 11:32:59

0001 /*
0002     SPDX-FileCopyrightText: 2010, 2012 Martin Gräßlin <mgraesslin@kde.org>
0003     SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "x11_standalone_egl_backend.h"
0009 #include "core/outputbackend.h"
0010 #include "core/overlaywindow.h"
0011 #include "core/renderloop_p.h"
0012 #include "kwinglplatform.h"
0013 #include "options.h"
0014 #include "scene/surfaceitem_x11.h"
0015 #include "scene/workspacescene.h"
0016 #include "softwarevsyncmonitor.h"
0017 #include "workspace.h"
0018 #include "x11_standalone_backend.h"
0019 #include "x11_standalone_logging.h"
0020 #include "x11_standalone_overlaywindow.h"
0021 
0022 #include <QOpenGLContext>
0023 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0024 #include <QtPlatformHeaders/QEGLNativeContext>
0025 #endif
0026 
0027 namespace KWin
0028 {
0029 
0030 EglLayer::EglLayer(EglBackend *backend)
0031     : m_backend(backend)
0032 {
0033 }
0034 
0035 std::optional<OutputLayerBeginFrameInfo> EglLayer::beginFrame()
0036 {
0037     return m_backend->beginFrame();
0038 }
0039 
0040 bool EglLayer::endFrame(const QRegion &renderedRegion, const QRegion &damagedRegion)
0041 {
0042     m_backend->endFrame(renderedRegion, damagedRegion);
0043     return true;
0044 }
0045 
0046 EglBackend::EglBackend(Display *display, X11StandaloneBackend *backend)
0047     : EglOnXBackend(kwinApp()->x11Connection(), display, kwinApp()->x11RootWindow())
0048     , m_backend(backend)
0049     , m_overlayWindow(std::make_unique<OverlayWindowX11>())
0050     , m_layer(std::make_unique<EglLayer>(this))
0051 {
0052     // There is no any way to determine when a buffer swap completes with EGL. Fallback
0053     // to software vblank events. Could we use the Present extension to get notified when
0054     // the overlay window is actually presented on the screen?
0055     m_vsyncMonitor = SoftwareVsyncMonitor::create();
0056     connect(backend->renderLoop(), &RenderLoop::refreshRateChanged, this, [this, backend]() {
0057         m_vsyncMonitor->setRefreshRate(backend->renderLoop()->refreshRate());
0058     });
0059     m_vsyncMonitor->setRefreshRate(backend->renderLoop()->refreshRate());
0060 
0061     connect(m_vsyncMonitor.get(), &VsyncMonitor::vblankOccurred, this, &EglBackend::vblank);
0062     connect(workspace(), &Workspace::geometryChanged, this, &EglBackend::screenGeometryChanged);
0063 }
0064 
0065 EglBackend::~EglBackend()
0066 {
0067     // No completion events will be received for in-flight frames, this may lock the
0068     // render loop. We need to ensure that the render loop is back to its initial state
0069     // if the render backend is about to be destroyed.
0070     RenderLoopPrivate::get(m_backend->renderLoop())->invalidate();
0071 
0072     if (isFailed() && m_overlayWindow) {
0073         m_overlayWindow->destroy();
0074     }
0075     cleanup();
0076 
0077     if (m_overlayWindow && m_overlayWindow->window()) {
0078         m_overlayWindow->destroy();
0079     }
0080 }
0081 
0082 std::unique_ptr<SurfaceTexture> EglBackend::createSurfaceTextureX11(SurfacePixmapX11 *texture)
0083 {
0084     return std::make_unique<EglSurfaceTextureX11>(this, texture);
0085 }
0086 
0087 void EglBackend::init()
0088 {
0089     QOpenGLContext *qtShareContext = QOpenGLContext::globalShareContext();
0090     EGLDisplay shareDisplay = EGL_NO_DISPLAY;
0091     EGLContext shareContext = EGL_NO_CONTEXT;
0092     if (qtShareContext) {
0093         qDebug(KWIN_X11STANDALONE) << "Global share context format:" << qtShareContext->format();
0094 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0095         const QVariant nativeHandle = qtShareContext->nativeHandle();
0096         if (!nativeHandle.canConvert<QEGLNativeContext>()) {
0097             setFailed(QStringLiteral("Invalid QOpenGLContext::globalShareContext()"));
0098             return;
0099         } else {
0100             QEGLNativeContext handle = qvariant_cast<QEGLNativeContext>(nativeHandle);
0101             shareContext = handle.context();
0102             shareDisplay = handle.display();
0103         }
0104 #else
0105         const auto nativeHandle = qtShareContext->nativeInterface<QNativeInterface::QEGLContext>();
0106         if (nativeHandle) {
0107             shareContext = nativeHandle->nativeContext();
0108             shareDisplay = nativeHandle->display();
0109         } else {
0110             setFailed(QStringLiteral("Invalid QOpenGLContext::globalShareContext()"));
0111             return;
0112         }
0113 #endif
0114     }
0115     if (shareContext == EGL_NO_CONTEXT) {
0116         setFailed(QStringLiteral("QOpenGLContext::globalShareContext() is required"));
0117         return;
0118     }
0119 
0120     m_fbo = std::make_unique<GLFramebuffer>(0, workspace()->geometry().size());
0121 
0122     kwinApp()->outputBackend()->setSceneEglDisplay(shareDisplay);
0123     kwinApp()->outputBackend()->setSceneEglGlobalShareContext(shareContext);
0124     EglOnXBackend::init();
0125 }
0126 
0127 bool EglBackend::createSurfaces()
0128 {
0129     if (!m_overlayWindow) {
0130         return false;
0131     }
0132 
0133     if (!m_overlayWindow->create()) {
0134         qCCritical(KWIN_X11STANDALONE) << "Could not get overlay window";
0135         return false;
0136     } else {
0137         m_overlayWindow->setup(XCB_WINDOW_NONE);
0138     }
0139 
0140     EGLSurface surface = createSurface(m_overlayWindow->window());
0141     if (surface == EGL_NO_SURFACE) {
0142         return false;
0143     }
0144     setSurface(surface);
0145     return true;
0146 }
0147 
0148 void EglBackend::screenGeometryChanged()
0149 {
0150     overlayWindow()->resize(workspace()->geometry().size());
0151 
0152     // The back buffer contents are now undefined
0153     m_bufferAge = 0;
0154     m_fbo = std::make_unique<GLFramebuffer>(0, workspace()->geometry().size());
0155 }
0156 
0157 OutputLayerBeginFrameInfo EglBackend::beginFrame()
0158 {
0159     makeCurrent();
0160 
0161     QRegion repaint;
0162     if (supportsBufferAge()) {
0163         repaint = m_damageJournal.accumulate(m_bufferAge, infiniteRegion());
0164     }
0165 
0166     eglWaitNative(EGL_CORE_NATIVE_ENGINE);
0167 
0168     return OutputLayerBeginFrameInfo{
0169         .renderTarget = RenderTarget(m_fbo.get()),
0170         .repaint = repaint,
0171     };
0172 }
0173 
0174 void EglBackend::endFrame(const QRegion &renderedRegion, const QRegion &damagedRegion)
0175 {
0176     // Save the damaged region to history
0177     if (supportsBufferAge()) {
0178         m_damageJournal.add(damagedRegion);
0179     }
0180     m_lastRenderedRegion = renderedRegion;
0181 }
0182 
0183 void EglBackend::present(Output *output)
0184 {
0185     // Start the software vsync monitor. There is no any reliable way to determine when
0186     // eglSwapBuffers() or eglSwapBuffersWithDamageEXT() completes.
0187     m_vsyncMonitor->arm();
0188 
0189     QRegion effectiveRenderedRegion = m_lastRenderedRegion;
0190     if (!GLPlatform::instance()->isGLES()) {
0191         const QRect displayRect = workspace()->geometry();
0192         if (!supportsBufferAge() && options->glPreferBufferSwap() == Options::CopyFrontBuffer && m_lastRenderedRegion != displayRect) {
0193             glReadBuffer(GL_FRONT);
0194             copyPixels(QRegion(displayRect) - m_lastRenderedRegion, displayRect.size());
0195             glReadBuffer(GL_BACK);
0196             effectiveRenderedRegion = displayRect;
0197         }
0198     }
0199 
0200     presentSurface(surface(), effectiveRenderedRegion, workspace()->geometry());
0201 
0202     if (overlayWindow() && overlayWindow()->window()) { // show the window only after the first pass,
0203         overlayWindow()->show(); // since that pass may take long
0204     }
0205 }
0206 
0207 void EglBackend::presentSurface(EGLSurface surface, const QRegion &damage, const QRect &screenGeometry)
0208 {
0209     const bool fullRepaint = supportsBufferAge() || (damage == screenGeometry);
0210 
0211     if (fullRepaint || !havePostSubBuffer()) {
0212         // the entire screen changed, or we cannot do partial updates (which implies we enabled surface preservation)
0213         eglSwapBuffers(eglDisplay(), surface);
0214         if (supportsBufferAge()) {
0215             eglQuerySurface(eglDisplay(), surface, EGL_BUFFER_AGE_EXT, &m_bufferAge);
0216         }
0217     } else {
0218         // a part of the screen changed, and we can use eglPostSubBufferNV to copy the updated area
0219         for (const QRect &r : damage) {
0220             eglPostSubBufferNV(eglDisplay(), surface, r.left(), screenGeometry.height() - r.bottom() - 1, r.width(), r.height());
0221         }
0222     }
0223 }
0224 
0225 OverlayWindow *EglBackend::overlayWindow() const
0226 {
0227     return m_overlayWindow.get();
0228 }
0229 
0230 OutputLayer *EglBackend::primaryLayer(Output *output)
0231 {
0232     return m_layer.get();
0233 }
0234 
0235 void EglBackend::vblank(std::chrono::nanoseconds timestamp)
0236 {
0237     RenderLoopPrivate *renderLoopPrivate = RenderLoopPrivate::get(m_backend->renderLoop());
0238     renderLoopPrivate->notifyFrameCompleted(timestamp);
0239 }
0240 
0241 EglSurfaceTextureX11::EglSurfaceTextureX11(EglBackend *backend, SurfacePixmapX11 *texture)
0242     : OpenGLSurfaceTextureX11(backend, texture)
0243 {
0244 }
0245 
0246 bool EglSurfaceTextureX11::create()
0247 {
0248     auto texture = std::make_unique<EglPixmapTexture>(static_cast<EglBackend *>(m_backend));
0249     if (texture->create(m_pixmap)) {
0250         m_texture = std::move(texture);
0251         return true;
0252     } else {
0253         return false;
0254     }
0255 }
0256 
0257 void EglSurfaceTextureX11::update(const QRegion &region)
0258 {
0259     // mipmaps need to be updated
0260     m_texture->setDirty();
0261 }
0262 
0263 EglPixmapTexture::EglPixmapTexture(EglBackend *backend)
0264     : GLTexture(*new EglPixmapTexturePrivate(this, backend))
0265 {
0266 }
0267 
0268 bool EglPixmapTexture::create(SurfacePixmapX11 *texture)
0269 {
0270     Q_D(EglPixmapTexture);
0271     return d->create(texture);
0272 }
0273 
0274 EglPixmapTexturePrivate::EglPixmapTexturePrivate(EglPixmapTexture *texture, EglBackend *backend)
0275     : q(texture)
0276     , m_backend(backend)
0277 {
0278     m_target = GL_TEXTURE_2D;
0279 }
0280 
0281 EglPixmapTexturePrivate::~EglPixmapTexturePrivate()
0282 {
0283     if (m_image != EGL_NO_IMAGE_KHR) {
0284         eglDestroyImageKHR(m_backend->eglDisplay(), m_image);
0285     }
0286 }
0287 
0288 bool EglPixmapTexturePrivate::create(SurfacePixmapX11 *pixmap)
0289 {
0290     const xcb_pixmap_t nativePixmap = pixmap->pixmap();
0291     if (nativePixmap == XCB_NONE) {
0292         return false;
0293     }
0294 
0295     glGenTextures(1, &m_texture);
0296     q->setWrapMode(GL_CLAMP_TO_EDGE);
0297     q->setFilter(GL_LINEAR);
0298     q->bind();
0299     const EGLint attribs[] = {
0300         EGL_IMAGE_PRESERVED_KHR, EGL_TRUE,
0301         EGL_NONE};
0302     m_image = eglCreateImageKHR(m_backend->eglDisplay(),
0303                                 EGL_NO_CONTEXT,
0304                                 EGL_NATIVE_PIXMAP_KHR,
0305                                 reinterpret_cast<EGLClientBuffer>(static_cast<uintptr_t>(nativePixmap)),
0306                                 attribs);
0307 
0308     if (EGL_NO_IMAGE_KHR == m_image) {
0309         qCDebug(KWIN_X11STANDALONE) << "failed to create egl image";
0310         q->unbind();
0311         return false;
0312     }
0313     glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, static_cast<GLeglImageOES>(m_image));
0314     q->unbind();
0315     q->setYInverted(true);
0316     m_size = pixmap->size();
0317     updateMatrix();
0318     return true;
0319 }
0320 
0321 void EglPixmapTexturePrivate::onDamage()
0322 {
0323     if (options->isGlStrictBinding()) {
0324         // This is just implemented to be consistent with
0325         // the example in mesa/demos/src/egl/opengles1/texture_from_pixmap.c
0326         eglWaitNative(EGL_CORE_NATIVE_ENGINE);
0327         glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, static_cast<GLeglImageOES>(m_image));
0328     }
0329     GLTexturePrivate::onDamage();
0330 }
0331 
0332 } // namespace KWin