File indexing completed on 2024-11-10 04:56:35

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 "compositor.h"
0010 #include "core/outputbackend.h"
0011 #include "core/outputlayer.h"
0012 #include "core/overlaywindow.h"
0013 #include "core/renderloop_p.h"
0014 #include "opengl/glplatform.h"
0015 #include "opengl/glrendertimequery.h"
0016 #include "options.h"
0017 #include "scene/surfaceitem_x11.h"
0018 #include "utils/c_ptr.h"
0019 #include "utils/softwarevsyncmonitor.h"
0020 #include "workspace.h"
0021 #include "x11_standalone_backend.h"
0022 #include "x11_standalone_logging.h"
0023 #include "x11_standalone_overlaywindow.h"
0024 
0025 #include <QOpenGLContext>
0026 #include <drm_fourcc.h>
0027 
0028 namespace KWin
0029 {
0030 
0031 EglLayer::EglLayer(EglBackend *backend)
0032     : m_backend(backend)
0033 {
0034 }
0035 
0036 std::optional<OutputLayerBeginFrameInfo> EglLayer::beginFrame()
0037 {
0038     return m_backend->beginFrame();
0039 }
0040 
0041 bool EglLayer::endFrame(const QRegion &renderedRegion, const QRegion &damagedRegion)
0042 {
0043     m_backend->endFrame(renderedRegion, damagedRegion);
0044     return true;
0045 }
0046 
0047 std::chrono::nanoseconds EglLayer::queryRenderTime() const
0048 {
0049     return m_backend->queryRenderTime();
0050 }
0051 
0052 EglBackend::EglBackend(::Display *display, X11StandaloneBackend *backend)
0053     : m_backend(backend)
0054     , m_overlayWindow(std::make_unique<OverlayWindowX11>(backend))
0055     , m_layer(std::make_unique<EglLayer>(this))
0056 {
0057     // There is no any way to determine when a buffer swap completes with EGL. Fallback
0058     // to software vblank events. Could we use the Present extension to get notified when
0059     // the overlay window is actually presented on the screen?
0060     m_vsyncMonitor = SoftwareVsyncMonitor::create();
0061     connect(backend->renderLoop(), &RenderLoop::refreshRateChanged, this, [this, backend]() {
0062         m_vsyncMonitor->setRefreshRate(backend->renderLoop()->refreshRate());
0063     });
0064     m_vsyncMonitor->setRefreshRate(backend->renderLoop()->refreshRate());
0065 
0066     connect(m_vsyncMonitor.get(), &VsyncMonitor::vblankOccurred, this, &EglBackend::vblank);
0067     Q_ASSERT(workspace());
0068     connect(workspace(), &Workspace::geometryChanged, this, &EglBackend::screenGeometryChanged);
0069     overlayWindow()->resize(workspace()->geometry().size());
0070 }
0071 
0072 EglBackend::~EglBackend()
0073 {
0074     // No completion events will be received for in-flight frames, this may lock the
0075     // render loop. We need to ensure that the render loop is back to its initial state
0076     // if the render backend is about to be destroyed.
0077     RenderLoopPrivate::get(m_backend->renderLoop())->invalidate();
0078 
0079     m_query.reset();
0080 
0081     if (isFailed() && m_overlayWindow) {
0082         m_overlayWindow->destroy();
0083     }
0084     cleanup();
0085 
0086     if (m_overlayWindow && m_overlayWindow->window()) {
0087         m_overlayWindow->destroy();
0088     }
0089 }
0090 
0091 std::unique_ptr<SurfaceTexture> EglBackend::createSurfaceTextureX11(SurfacePixmapX11 *texture)
0092 {
0093     return std::make_unique<EglSurfaceTextureX11>(this, texture);
0094 }
0095 
0096 void EglBackend::init()
0097 {
0098     QOpenGLContext *qtShareContext = QOpenGLContext::globalShareContext();
0099     ::EGLDisplay shareDisplay = EGL_NO_DISPLAY;
0100     ::EGLContext shareContext = EGL_NO_CONTEXT;
0101     if (qtShareContext) {
0102         qDebug(KWIN_X11STANDALONE) << "Global share context format:" << qtShareContext->format();
0103         const auto nativeHandle = qtShareContext->nativeInterface<QNativeInterface::QEGLContext>();
0104         if (nativeHandle) {
0105             shareContext = nativeHandle->nativeContext();
0106             shareDisplay = nativeHandle->display();
0107         } else {
0108             setFailed(QStringLiteral("Invalid QOpenGLContext::globalShareContext()"));
0109             return;
0110         }
0111     }
0112     if (shareContext == EGL_NO_CONTEXT) {
0113         setFailed(QStringLiteral("QOpenGLContext::globalShareContext() is required"));
0114         return;
0115     }
0116 
0117     m_fbo = std::make_unique<GLFramebuffer>(0, workspace()->geometry().size());
0118 
0119     m_backend->setEglDisplay(EglDisplay::create(shareDisplay, false));
0120     kwinApp()->outputBackend()->setSceneEglGlobalShareContext(shareContext);
0121 
0122     qputenv("EGL_PLATFORM", "x11");
0123     if (!initRenderingContext()) {
0124         setFailed(QStringLiteral("Could not initialize rendering context"));
0125         return;
0126     }
0127 
0128     initKWinGL();
0129 
0130     if (!hasExtension(QByteArrayLiteral("EGL_KHR_image")) && (!hasExtension(QByteArrayLiteral("EGL_KHR_image_base")) || !hasExtension(QByteArrayLiteral("EGL_KHR_image_pixmap")))) {
0131         setFailed(QStringLiteral("Required support for binding pixmaps to EGLImages not found, disabling compositing"));
0132         return;
0133     }
0134     if (!hasGLExtension(QByteArrayLiteral("GL_OES_EGL_image"))) {
0135         setFailed(QStringLiteral("Required extension GL_OES_EGL_image not found, disabling compositing"));
0136         return;
0137     }
0138 
0139     // check for EGL_NV_post_sub_buffer and whether it can be used on the surface
0140     if (hasExtension(QByteArrayLiteral("EGL_NV_post_sub_buffer"))) {
0141         if (eglQuerySurface(eglDisplayObject()->handle(), surface(), EGL_POST_SUB_BUFFER_SUPPORTED_NV, &m_havePostSubBuffer) == EGL_FALSE) {
0142             EGLint error = eglGetError();
0143             if (error != EGL_SUCCESS && error != EGL_BAD_ATTRIBUTE) {
0144                 setFailed(QStringLiteral("query surface failed"));
0145                 return;
0146             } else {
0147                 m_havePostSubBuffer = EGL_FALSE;
0148             }
0149         }
0150     }
0151 
0152     if (m_havePostSubBuffer) {
0153         qCDebug(KWIN_CORE) << "EGL implementation and surface support eglPostSubBufferNV, let's use it";
0154 
0155         // check if swap interval 1 is supported
0156         EGLint val;
0157         eglGetConfigAttrib(eglDisplayObject()->handle(), config(), EGL_MAX_SWAP_INTERVAL, &val);
0158         if (val >= 1) {
0159             if (eglSwapInterval(eglDisplayObject()->handle(), 1)) {
0160                 qCDebug(KWIN_CORE) << "Enabled v-sync";
0161             }
0162         } else {
0163             qCWarning(KWIN_CORE) << "Cannot enable v-sync as max. swap interval is" << val;
0164         }
0165     } else {
0166         /* In the GLX backend, we fall back to using glCopyPixels if we have no extension providing support for partial screen updates.
0167          * However, that does not work in EGL - glCopyPixels with glDrawBuffer(GL_FRONT); does nothing.
0168          * Hence we need EGL to preserve the backbuffer for us, so that we can draw the partial updates on it and call
0169          * eglSwapBuffers() for each frame. eglSwapBuffers() then does the copy (no page flip possible in this mode),
0170          * which means it is slow and not synced to the v-blank. */
0171         qCWarning(KWIN_CORE) << "eglPostSubBufferNV not supported, have to enable buffer preservation - which breaks v-sync and performance";
0172         eglSurfaceAttrib(eglDisplayObject()->handle(), surface(), EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED);
0173     }
0174 
0175     m_swapStrategy = options->glPreferBufferSwap();
0176     if (m_swapStrategy == Options::AutoSwapStrategy) {
0177         // buffer copying is very fast with the nvidia blob
0178         // but due to restrictions in DRI2 *incredibly* slow for all MESA drivers
0179         // see https://www.x.org/releases/X11R7.7/doc/dri2proto/dri2proto.txt, item 2.5
0180         if (GLPlatform::instance()->driver() == Driver_NVidia) {
0181             m_swapStrategy = Options::CopyFrontBuffer;
0182         } else if (GLPlatform::instance()->driver() != Driver_Unknown) { // undetected, finally resolved when context is initialized
0183             m_swapStrategy = Options::ExtendDamage;
0184         }
0185     }
0186 }
0187 
0188 bool EglBackend::initRenderingContext()
0189 {
0190     initClientExtensions();
0191     auto display = kwinApp()->outputBackend()->sceneEglDisplayObject();
0192 
0193     // Use eglGetPlatformDisplayEXT() to get the display pointer
0194     // if the implementation supports it.
0195     if (!display) {
0196         m_havePlatformBase = hasClientExtension(QByteArrayLiteral("EGL_EXT_platform_base"));
0197         if (m_havePlatformBase) {
0198             // Make sure that the X11 platform is supported
0199             if (!hasClientExtension(QByteArrayLiteral("EGL_EXT_platform_x11")) && !hasClientExtension(QByteArrayLiteral("EGL_KHR_platform_x11"))) {
0200                 qCWarning(KWIN_CORE) << "EGL_EXT_platform_base is supported, but neither EGL_EXT_platform_x11 nor EGL_KHR_platform_x11 is supported."
0201                                      << "Cannot create EGLDisplay on X11";
0202                 return false;
0203             }
0204 
0205             m_backend->setEglDisplay(EglDisplay::create(eglGetPlatformDisplayEXT(EGL_PLATFORM_X11_EXT, m_backend->display(), nullptr)));
0206         } else {
0207             m_backend->setEglDisplay(EglDisplay::create(eglGetDisplay(m_backend->display())));
0208         }
0209         display = m_backend->sceneEglDisplayObject();
0210         if (!display) {
0211             qCWarning(KWIN_CORE) << "Failed to get the EGLDisplay";
0212             return false;
0213         }
0214     }
0215 
0216     setEglDisplay(display);
0217 
0218     if (!createContext(chooseBufferConfig())) {
0219         qCCritical(KWIN_CORE) << "Create OpenGL context failed";
0220         return false;
0221     }
0222 
0223     if (!m_overlayWindow->create()) {
0224         qCCritical(KWIN_X11STANDALONE) << "Could not get overlay window";
0225         return false;
0226     } else {
0227         m_overlayWindow->setup(XCB_WINDOW_NONE);
0228     }
0229 
0230     EGLSurface surface = createSurface(m_overlayWindow->window());
0231     if (surface == EGL_NO_SURFACE) {
0232         qCCritical(KWIN_CORE) << "Creating egl surface failed";
0233         return false;
0234     }
0235     setSurface(surface);
0236 
0237     if (!makeCurrent()) {
0238         qCCritical(KWIN_CORE) << "Make Context Current failed";
0239         return false;
0240     }
0241 
0242     EGLint error = eglGetError();
0243     if (error != EGL_SUCCESS) {
0244         qCWarning(KWIN_CORE) << "Error occurred while creating context " << error;
0245         return false;
0246     }
0247 
0248     return true;
0249 }
0250 
0251 EGLSurface EglBackend::createSurface(xcb_window_t window)
0252 {
0253     if (window == XCB_WINDOW_NONE) {
0254         return EGL_NO_SURFACE;
0255     }
0256 
0257     // Window is 64 bits on a 64-bit architecture whereas xcb_window_t is always 32 bits.
0258     ::Window nativeWindow = window;
0259 
0260     EGLSurface surface = EGL_NO_SURFACE;
0261     if (m_havePlatformBase) {
0262         // eglCreatePlatformWindowSurfaceEXT() expects a pointer to the Window.
0263         surface = eglCreatePlatformWindowSurfaceEXT(eglDisplayObject()->handle(), config(), (void *)&nativeWindow, nullptr);
0264     } else {
0265         // eglCreateWindowSurface() expects a Window, not a pointer to the Window. Use
0266         // a c style cast as there are (buggy) platforms where the size of the Window
0267         // type is not the same as the size of EGLNativeWindowType, reinterpret_cast<>()
0268         // may not compile.
0269         surface = eglCreateWindowSurface(eglDisplayObject()->handle(), config(), (EGLNativeWindowType)(uintptr_t)nativeWindow, nullptr);
0270     }
0271 
0272     return surface;
0273 }
0274 
0275 EGLConfig EglBackend::chooseBufferConfig()
0276 {
0277     const EGLint config_attribs[] = {
0278         EGL_SURFACE_TYPE,
0279         EGL_WINDOW_BIT | (supportsBufferAge() ? 0 : EGL_SWAP_BEHAVIOR_PRESERVED_BIT),
0280         EGL_RED_SIZE,
0281         1,
0282         EGL_GREEN_SIZE,
0283         1,
0284         EGL_BLUE_SIZE,
0285         1,
0286         EGL_ALPHA_SIZE,
0287         0,
0288         EGL_RENDERABLE_TYPE,
0289         isOpenGLES() ? EGL_OPENGL_ES2_BIT : EGL_OPENGL_BIT,
0290         EGL_CONFIG_CAVEAT,
0291         EGL_NONE,
0292         EGL_NONE,
0293     };
0294 
0295     EGLint count;
0296     EGLConfig configs[1024];
0297     if (eglChooseConfig(eglDisplayObject()->handle(), config_attribs, configs, 1024, &count) == EGL_FALSE) {
0298         qCCritical(KWIN_CORE) << "choose config failed";
0299         return EGL_NO_CONFIG_KHR;
0300     }
0301 
0302     UniqueCPtr<xcb_get_window_attributes_reply_t> attribs(xcb_get_window_attributes_reply(m_backend->connection(),
0303                                                                                           xcb_get_window_attributes_unchecked(m_backend->connection(), m_backend->rootWindow()),
0304                                                                                           nullptr));
0305     if (!attribs) {
0306         qCCritical(KWIN_CORE) << "Failed to get window attributes of root window";
0307         return EGL_NO_CONFIG_KHR;
0308     }
0309 
0310     for (int i = 0; i < count; i++) {
0311         EGLint val;
0312         if (eglGetConfigAttrib(eglDisplayObject()->handle(), configs[i], EGL_NATIVE_VISUAL_ID, &val) == EGL_FALSE) {
0313             qCCritical(KWIN_CORE) << "egl get config attrib failed";
0314         }
0315         if (uint32_t(val) == attribs->visual) {
0316             return configs[i];
0317         }
0318     }
0319     return configs[0];
0320 }
0321 
0322 void EglBackend::screenGeometryChanged()
0323 {
0324     overlayWindow()->resize(workspace()->geometry().size());
0325 
0326     // The back buffer contents are now undefined
0327     m_bufferAge = 0;
0328     m_fbo = std::make_unique<GLFramebuffer>(0, workspace()->geometry().size());
0329 }
0330 
0331 OutputLayerBeginFrameInfo EglBackend::beginFrame()
0332 {
0333     makeCurrent();
0334 
0335     QRegion repaint;
0336     if (supportsBufferAge()) {
0337         repaint = m_damageJournal.accumulate(m_bufferAge, infiniteRegion());
0338     }
0339     eglWaitNative(EGL_CORE_NATIVE_ENGINE);
0340 
0341     if (!m_query) {
0342         m_query = std::make_unique<GLRenderTimeQuery>();
0343     }
0344     m_query->begin();
0345     return OutputLayerBeginFrameInfo{
0346         .renderTarget = RenderTarget(m_fbo.get()),
0347         .repaint = repaint,
0348     };
0349 }
0350 
0351 void EglBackend::endFrame(const QRegion &renderedRegion, const QRegion &damagedRegion)
0352 {
0353     m_query->end();
0354     // Save the damaged region to history
0355     if (supportsBufferAge()) {
0356         m_damageJournal.add(damagedRegion);
0357     }
0358     m_lastRenderedRegion = renderedRegion;
0359 }
0360 
0361 void EglBackend::present(Output *output, const std::shared_ptr<OutputFrame> &frame)
0362 {
0363     m_frame = frame;
0364     // Start the software vsync monitor. There is no any reliable way to determine when
0365     // eglSwapBuffers() or eglSwapBuffersWithDamageEXT() completes.
0366     m_vsyncMonitor->arm();
0367 
0368     QRegion effectiveRenderedRegion = m_lastRenderedRegion;
0369     if (!GLPlatform::instance()->isGLES()) {
0370         const QRect displayRect = workspace()->geometry();
0371         if (!supportsBufferAge() && m_swapStrategy == Options::CopyFrontBuffer && m_lastRenderedRegion != displayRect) {
0372             glReadBuffer(GL_FRONT);
0373             copyPixels(QRegion(displayRect) - m_lastRenderedRegion, displayRect.size());
0374             glReadBuffer(GL_BACK);
0375             effectiveRenderedRegion = displayRect;
0376         }
0377     }
0378 
0379     presentSurface(surface(), effectiveRenderedRegion, workspace()->geometry());
0380 
0381     if (overlayWindow() && overlayWindow()->window()) { // show the window only after the first pass,
0382         overlayWindow()->show(); // since that pass may take long
0383     }
0384 }
0385 
0386 void EglBackend::presentSurface(EGLSurface surface, const QRegion &damage, const QRect &screenGeometry)
0387 {
0388     const bool fullRepaint = supportsBufferAge() || (damage == screenGeometry);
0389 
0390     if (fullRepaint || !m_havePostSubBuffer) {
0391         // the entire screen changed, or we cannot do partial updates (which implies we enabled surface preservation)
0392         eglSwapBuffers(eglDisplayObject()->handle(), surface);
0393         if (supportsBufferAge()) {
0394             eglQuerySurface(eglDisplayObject()->handle(), surface, EGL_BUFFER_AGE_EXT, &m_bufferAge);
0395         }
0396     } else {
0397         // a part of the screen changed, and we can use eglPostSubBufferNV to copy the updated area
0398         for (const QRect &r : damage) {
0399             eglPostSubBufferNV(eglDisplayObject()->handle(), surface, r.left(), screenGeometry.height() - r.bottom() - 1, r.width(), r.height());
0400         }
0401     }
0402 }
0403 
0404 OverlayWindow *EglBackend::overlayWindow() const
0405 {
0406     return m_overlayWindow.get();
0407 }
0408 
0409 OutputLayer *EglBackend::primaryLayer(Output *output)
0410 {
0411     return m_layer.get();
0412 }
0413 
0414 std::chrono::nanoseconds EglBackend::queryRenderTime()
0415 {
0416     makeCurrent();
0417     return m_query->result();
0418 }
0419 
0420 void EglBackend::vblank(std::chrono::nanoseconds timestamp)
0421 {
0422     m_frame->presented(std::chrono::nanoseconds::zero(), timestamp, queryRenderTime(), PresentationMode::VSync);
0423     m_frame.reset();
0424 }
0425 
0426 EglSurfaceTextureX11::EglSurfaceTextureX11(EglBackend *backend, SurfacePixmapX11 *texture)
0427     : OpenGLSurfaceTextureX11(backend, texture)
0428 {
0429 }
0430 
0431 bool EglSurfaceTextureX11::create()
0432 {
0433     auto texture = std::make_shared<EglPixmapTexture>(static_cast<EglBackend *>(m_backend));
0434     if (texture->create(m_pixmap)) {
0435         m_texture = {texture};
0436         return true;
0437     } else {
0438         return false;
0439     }
0440 }
0441 
0442 void EglSurfaceTextureX11::update(const QRegion &region)
0443 {
0444     // mipmaps need to be updated
0445     m_texture.setDirty();
0446 }
0447 
0448 EglPixmapTexture::EglPixmapTexture(EglBackend *backend)
0449     : GLTexture(GL_TEXTURE_2D)
0450     , m_backend(backend)
0451 {
0452 }
0453 
0454 EglPixmapTexture::~EglPixmapTexture()
0455 {
0456     if (m_image != EGL_NO_IMAGE_KHR) {
0457         eglDestroyImageKHR(m_backend->eglDisplayObject()->handle(), m_image);
0458     }
0459 }
0460 
0461 bool EglPixmapTexture::create(SurfacePixmapX11 *pixmap)
0462 {
0463     const xcb_pixmap_t nativePixmap = pixmap->pixmap();
0464     if (nativePixmap == XCB_NONE) {
0465         return false;
0466     }
0467 
0468     glGenTextures(1, &d->m_texture);
0469     setWrapMode(GL_CLAMP_TO_EDGE);
0470     setFilter(GL_LINEAR);
0471     bind();
0472     const EGLint attribs[] = {
0473         EGL_IMAGE_PRESERVED_KHR, EGL_TRUE,
0474         EGL_NONE};
0475     m_image = eglCreateImageKHR(m_backend->eglDisplayObject()->handle(),
0476                                 EGL_NO_CONTEXT,
0477                                 EGL_NATIVE_PIXMAP_KHR,
0478                                 reinterpret_cast<EGLClientBuffer>(static_cast<uintptr_t>(nativePixmap)),
0479                                 attribs);
0480 
0481     if (EGL_NO_IMAGE_KHR == m_image) {
0482         qCDebug(KWIN_X11STANDALONE) << "failed to create egl image";
0483         unbind();
0484         return false;
0485     }
0486     glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, static_cast<GLeglImageOES>(m_image));
0487     unbind();
0488     setContentTransform(OutputTransform::FlipY);
0489     d->m_size = pixmap->size();
0490     d->updateMatrix();
0491     return true;
0492 }
0493 
0494 void EglPixmapTexture::onDamage()
0495 {
0496     if (options->isGlStrictBinding()) {
0497         // This is just implemented to be consistent with
0498         // the example in mesa/demos/src/egl/opengles1/texture_from_pixmap.c
0499         eglWaitNative(EGL_CORE_NATIVE_ENGINE);
0500         glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, static_cast<GLeglImageOES>(m_image));
0501     }
0502 }
0503 
0504 } // namespace KWin
0505 
0506 #include "moc_x11_standalone_egl_backend.cpp"