diff --git a/plugins/platforms/x11/standalone/glxbackend.h b/plugins/platforms/x11/standalone/glxbackend.h --- a/plugins/platforms/x11/standalone/glxbackend.h +++ b/plugins/platforms/x11/standalone/glxbackend.h @@ -21,7 +21,6 @@ #define KWIN_GLX_BACKEND_H #include "backend.h" #include "texture.h" -#include "swap_profiler.h" #include "x11eventfilter.h" #include @@ -32,10 +31,6 @@ namespace KWin { -// GLX_MESA_swap_interval -using glXSwapIntervalMESA_func = int (*)(unsigned int interval); -extern glXSwapIntervalMESA_func glXSwapIntervalMESA; - class FBConfigInfo { public: @@ -47,9 +42,6 @@ }; -// ------------------------------------------------------------------ - - class SwapEventFilter : public X11EventFilter { public: @@ -84,14 +76,18 @@ void present() override; private: - bool initBuffer(); bool checkVersion(); + + bool initBuffer(); void initExtensions(); - void waitSync(); bool initRenderingContext(); bool initFbConfig(); void initVisualDepthHashTable(); - void setSwapInterval(int interval); + + void setVsync(bool enable); + void swap(); + void copy(); + Display *display() const { return m_x11Display; } @@ -111,15 +107,12 @@ QHash m_visualDepthHash; std::unique_ptr m_swapEventFilter; int m_bufferAge; + + bool m_haveOMLSyncControl = false; bool m_haveMESACopySubBuffer = false; - bool m_haveMESASwapControl = false; - bool m_haveEXTSwapControl = false; - bool m_haveSGISwapControl = false; bool m_haveINTELSwapEvent = false; - bool haveSwapInterval = false; - bool haveWaitSync = false; + Display *m_x11Display; - SwapProfiler m_swapProfiler; friend class GlxTexture; }; diff --git a/plugins/platforms/x11/standalone/glxbackend.cpp b/plugins/platforms/x11/standalone/glxbackend.cpp --- a/plugins/platforms/x11/standalone/glxbackend.cpp +++ b/plugins/platforms/x11/standalone/glxbackend.cpp @@ -99,20 +99,14 @@ return false; } - -// ----------------------------------------------------------------------- - - - GlxBackend::GlxBackend(Display *display) : OpenGLBackend() , m_overlayWindow(kwinApp()->platform()->createOverlayWindow()) , window(None) , fbconfig(NULL) , glxWindow(None) , ctx(nullptr) , m_bufferAge(0) - , haveSwapInterval(false) , m_x11Display(display) { // Ensures calls to glXSwapBuffers will always block until the next @@ -126,9 +120,6 @@ QOpenGLContext::supportsThreadedOpenGL(); } -static bool gs_tripleBufferUndetected = true; -static bool gs_tripleBufferNeedsDetection = false; - GlxBackend::~GlxBackend() { if (isFailed()) { @@ -139,9 +130,6 @@ cleanupGL(); doneCurrent(); - gs_tripleBufferUndetected = true; - gs_tripleBufferNeedsDetection = false; - if (ctx) glXDestroyContext(display(), ctx); @@ -172,7 +160,6 @@ #endif return ret; } -glXSwapIntervalMESA_func glXSwapIntervalMESA; void GlxBackend::init() { @@ -183,14 +170,6 @@ } initExtensions(); - - // resolve glXSwapIntervalMESA if available - if (hasExtension(QByteArrayLiteral("GLX_MESA_swap_control"))) { - glXSwapIntervalMESA = (glXSwapIntervalMESA_func) getProcAddress("glXSwapIntervalMESA"); - } else { - glXSwapIntervalMESA = nullptr; - } - initVisualDepthHashTable(); if (!initBuffer()) { @@ -213,60 +192,42 @@ initGL(&getProcAddress); // Check whether certain features are supported + m_haveOMLSyncControl = hasExtension(QByteArrayLiteral("GLX_OML_sync_control")); m_haveMESACopySubBuffer = hasExtension(QByteArrayLiteral("GLX_MESA_copy_sub_buffer")); - m_haveMESASwapControl = hasExtension(QByteArrayLiteral("GLX_MESA_swap_control")); - m_haveEXTSwapControl = hasExtension(QByteArrayLiteral("GLX_EXT_swap_control")); - m_haveSGISwapControl = hasExtension(QByteArrayLiteral("GLX_SGI_swap_control")); + // only enable Intel swap event if env variable is set, see BUG 342582 m_haveINTELSwapEvent = hasExtension(QByteArrayLiteral("GLX_INTEL_swap_event")) - && qgetenv("KWIN_USE_INTEL_SWAP_EVENT") == QByteArrayLiteral("1"); + && qgetenv("KWIN_USE_INTEL_SWAP_EVENT") != QByteArrayLiteral("0"); if (m_haveINTELSwapEvent) { m_swapEventFilter = std::make_unique(window, glxWindow); glXSelectEvent(display(), glxWindow, GLX_BUFFER_SWAP_COMPLETE_INTEL_MASK); } - haveSwapInterval = m_haveMESASwapControl || m_haveEXTSwapControl || m_haveSGISwapControl; - setSupportsBufferAge(false); - if (hasExtension(QByteArrayLiteral("GLX_EXT_buffer_age"))) { const QByteArray useBufferAge = qgetenv("KWIN_USE_BUFFER_AGE"); - - if (useBufferAge != "0") + if (useBufferAge != "0") { setSupportsBufferAge(true); + } } setSyncsToVBlank(false); setBlocksForRetrace(false); - haveWaitSync = false; - gs_tripleBufferNeedsDetection = false; - m_swapProfiler.init(); + const bool wantSync = options->glPreferBufferSwap() != Options::NoSwapEncourage; if (wantSync && glXIsDirect(display(), ctx)) { - if (haveSwapInterval) { // glXSwapInterval is preferred being more reliable - setSwapInterval(1); + if (m_haveOMLSyncControl) { // glXSwapInterval is preferred being more reliable + setVsync(true); setSyncsToVBlank(true); - const QByteArray tripleBuffer = qgetenv("KWIN_TRIPLE_BUFFER"); - if (!tripleBuffer.isEmpty()) { - setBlocksForRetrace(qstrcmp(tripleBuffer, "0") == 0); - gs_tripleBufferUndetected = false; - } - gs_tripleBufferNeedsDetection = gs_tripleBufferUndetected; - } else if (hasExtension(QByteArrayLiteral("GLX_SGI_video_sync"))) { - unsigned int sync; - if (glXGetVideoSyncSGI(&sync) == 0 && glXWaitVideoSyncSGI(1, 0, &sync) == 0) { - setSyncsToVBlank(true); - setBlocksForRetrace(true); - haveWaitSync = true; - } else - qCWarning(KWIN_X11STANDALONE) << "NO VSYNC! glXSwapInterval is not supported, glXWaitVideoSync is supported but broken"; - } else + } else { qCWarning(KWIN_X11STANDALONE) << "NO VSYNC! neither glSwapInterval nor glXWaitVideoSync are supported"; + } } else { // disable v-sync (if possible) - setSwapInterval(0); + setVsync(false); } + if (glPlatform->isVirtualBox()) { // VirtualBox does not support glxQueryDrawable // this should actually be in kwinglutils_funcs, but QueryDrawable seems not to be provided by an extension @@ -279,6 +240,20 @@ qCDebug(KWIN_X11STANDALONE) << "Direct rendering:" << isDirectRendering(); } +void GlxBackend::setVsync(bool enable) +{ + if (m_haveOMLSyncControl) { + // TODO? + } +// const int interval = enable ? 1 : 0; +// if (m_haveEXTSwapControl) +// glXSwapIntervalEXT(display(), glxWindow, interval); +// else if (m_haveMESASwapControl) +// glXSwapIntervalMESA(interval); +// else if (m_haveSGISwapControl) +// glXSwapIntervalSGI(interval); +} + bool GlxBackend::checkVersion() { int major, minor; @@ -459,7 +434,8 @@ if (count > 0) XFree(configs); - std::stable_sort(candidates.begin(), candidates.end(), [](const FBConfig &left, const FBConfig &right) { + std::stable_sort(candidates.begin(), candidates.end(), + [](const FBConfig &left, const FBConfig &right) { if (left.depth < right.depth) return true; @@ -483,8 +459,10 @@ glXGetFBConfigAttrib(display(), fbconfig, GLX_STENCIL_SIZE, &stencil); glXGetFBConfigAttrib(display(), fbconfig, GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB, &srgb); - qCDebug(KWIN_X11STANDALONE, "Choosing GLXFBConfig %#x X visual %#x depth %d RGBA %d:%d:%d:%d ZS %d:%d sRGB: %d", - fbconfig_id, visual_id, visualDepth(visual_id), red, green, blue, alpha, depth, stencil, srgb); + qCDebug(KWIN_X11STANDALONE, + "Choosing GLXFBConfig %#x X visual %#x depth %d RGBA %d:%d:%d:%d ZS %d:%d sRGB: %d", + fbconfig_id, visual_id, visualDepth(visual_id), + red, green, blue, alpha, depth, stencil, srgb); } if (fbconfig == nullptr) { @@ -672,85 +650,6 @@ return info; } -void GlxBackend::setSwapInterval(int interval) -{ - if (m_haveEXTSwapControl) - glXSwapIntervalEXT(display(), glxWindow, interval); - else if (m_haveMESASwapControl) - glXSwapIntervalMESA(interval); - else if (m_haveSGISwapControl) - glXSwapIntervalSGI(interval); -} - -void GlxBackend::waitSync() -{ - // NOTE that vsync has no effect with indirect rendering - if (haveWaitSync) { - uint sync; -#if 0 - // TODO: why precisely is this important? - // the sync counter /can/ perform multiple steps during glXGetVideoSync & glXWaitVideoSync - // but this only leads to waiting for two frames??!? - glXGetVideoSync(&sync); - glXWaitVideoSync(2, (sync + 1) % 2, &sync); -#else - glXWaitVideoSyncSGI(1, 0, &sync); -#endif - } -} - -void GlxBackend::present() -{ - if (lastDamage().isEmpty()) - return; - - const QSize &screenSize = screens()->size(); - const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height()); - const bool fullRepaint = supportsBufferAge() || (lastDamage() == displayRegion); - - if (fullRepaint) { - if (m_haveINTELSwapEvent) - Compositor::self()->aboutToSwapBuffers(); - - if (haveSwapInterval) { - if (gs_tripleBufferNeedsDetection) { - glXWaitGL(); - m_swapProfiler.begin(); - } - glXSwapBuffers(display(), glxWindow); - if (gs_tripleBufferNeedsDetection) { - glXWaitGL(); - if (char result = m_swapProfiler.end()) { - gs_tripleBufferUndetected = gs_tripleBufferNeedsDetection = false; - setBlocksForRetrace(result == 'd'); - } - } - } else { - waitSync(); - glXSwapBuffers(display(), glxWindow); - } - if (supportsBufferAge()) { - glXQueryDrawable(display(), glxWindow, GLX_BACK_BUFFER_AGE_EXT, (GLuint *) &m_bufferAge); - } - } else if (m_haveMESACopySubBuffer) { - for (const QRect &r : lastDamage()) { - // convert to OpenGL coordinates - int y = screenSize.height() - r.y() - r.height(); - glXCopySubBufferMESA(display(), glxWindow, r.x(), y, r.width(), r.height()); - } - } else { // Copy Pixels (horribly slow on Mesa) - glDrawBuffer(GL_FRONT); - copyPixels(lastDamage()); - glDrawBuffer(GL_BACK); - } - - setLastDamage(QRegion()); - if (!supportsBufferAge()) { - glXWaitGL(); - XFlush(display()); - } -} - void GlxBackend::screenGeometryChanged(const QSize &size) { doneCurrent(); @@ -766,35 +665,106 @@ m_bufferAge = 0; } -SceneOpenGLTexturePrivate *GlxBackend::createBackendTexture(SceneOpenGLTexture *texture) +bool GlxBackend::makeCurrent() { - return new GlxTexture(texture, this); + if (QOpenGLContext *context = QOpenGLContext::currentContext()) { + // Workaround to tell Qt that no QOpenGLContext is current + context->doneCurrent(); + } + const bool current = glXMakeCurrent(display(), glxWindow, ctx); + return current; } -QRegion GlxBackend::prepareRenderingFrame() +void GlxBackend::doneCurrent() { - QRegion repaint; + glXMakeCurrent(display(), None, nullptr); +} - if (gs_tripleBufferNeedsDetection) { - // the composite timer floors the repaint frequency. This can pollute our triple buffering - // detection because the glXSwapBuffers call for the new frame has to wait until the pending - // one scanned out. - // So we compensate for that by waiting an extra milisecond to give the driver the chance to - // fllush the buffer queue - usleep(1000); - } +OverlayWindow* GlxBackend::overlayWindow() const +{ + return m_overlayWindow; +} + +bool GlxBackend::usesOverlayWindow() const +{ + return true; +} +QRegion GlxBackend::prepareRenderingFrame() +{ present(); - if (supportsBufferAge()) - repaint = accumulatedDamageHistory(m_bufferAge); + const auto repaint = supportsBufferAge() ? accumulatedDamageHistory(m_bufferAge) : QRegion(); startRenderTimer(); glXWaitX(); return repaint; } +void GlxBackend::swap() +{ + if (m_haveINTELSwapEvent) { + Compositor::self()->aboutToSwapBuffers(); + } + glXSwapBuffers(display(), glxWindow); + if (supportsBufferAge()) { + glXQueryDrawable(display(), glxWindow, GLX_BACK_BUFFER_AGE_EXT, (GLuint *) &m_bufferAge); + } +} + +void GlxBackend::copy() +{ + if (m_haveMESACopySubBuffer) { + // Optimized copy is possible. + for (const QRect &r : lastDamage()) { + // convert to OpenGL coordinates + int y = screens()->size().height() - r.y() - r.height(); + glXCopySubBufferMESA(display(), glxWindow, r.x(), y, r.width(), r.height()); + } + return; + } + + // Copy Pixels (horribly slow on Mesa) + glDrawBuffer(GL_FRONT); + copyPixels(lastDamage()); + glDrawBuffer(GL_BACK); +} + +void GlxBackend::present() +{ + if (lastDamage().isEmpty()) { + return; + } + + const QSize &screenSize = screens()->size(); + const bool isFullRepaint = (lastDamage() == + QRegion(0, 0, screenSize.width(), screenSize.height())); + + if (supportsBufferAge() || isFullRepaint) { + // Swap is possible because of full repaint or buffer age support. + // TODO: Why is buffer age so important for that? We can also swap without it. + swap(); + } else { + copy(); + } + + if (!m_haveINTELSwapEvent) { + // We don't get an X event when the swap has finished, so we just block here + // until it has. + // TODO: To not block too much we should delay the present with a timer to the end + // of the vblank cycle. + glFinish(); + } + + setLastDamage(QRegion()); + + if (!supportsBufferAge()) { + glXWaitGL(); + XFlush(display()); + } +} + void GlxBackend::endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) { if (damagedRegion.isEmpty()) { @@ -816,47 +786,19 @@ setLastDamage(renderedRegion); - if (!blocksForRetrace()) { - // This also sets lastDamage to empty which prevents the frame from - // being posted again when prepareRenderingFrame() is called. - present(); - } else { - // Make sure that the GPU begins processing the command stream - // now and not the next time prepareRenderingFrame() is called. - glFlush(); - } - - if (overlayWindow()->window()) // show the window only after the first pass, - overlayWindow()->show(); // since that pass may take long + // Show the window only after the first pass, + // since that pass may take long. + if (overlayWindow()->window()) + overlayWindow()->show(); // Save the damaged region to history if (supportsBufferAge()) addToDamageHistory(damagedRegion); } -bool GlxBackend::makeCurrent() -{ - if (QOpenGLContext *context = QOpenGLContext::currentContext()) { - // Workaround to tell Qt that no QOpenGLContext is current - context->doneCurrent(); - } - const bool current = glXMakeCurrent(display(), glxWindow, ctx); - return current; -} - -void GlxBackend::doneCurrent() -{ - glXMakeCurrent(display(), None, nullptr); -} - -OverlayWindow* GlxBackend::overlayWindow() const -{ - return m_overlayWindow; -} - -bool GlxBackend::usesOverlayWindow() const +SceneOpenGLTexturePrivate *GlxBackend::createBackendTexture(SceneOpenGLTexture *texture) { - return true; + return new GlxTexture(texture, this); } /********************************************************