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