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 ®ion) 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