diff --git a/platformsupport/scenes/opengl/abstract_egl_backend.cpp b/platformsupport/scenes/opengl/abstract_egl_backend.cpp index f37634dd22..e7ca0aba0d 100644 --- a/platformsupport/scenes/opengl/abstract_egl_backend.cpp +++ b/platformsupport/scenes/opengl/abstract_egl_backend.cpp @@ -133,6 +133,9 @@ void AbstractEglBackend::initBufferAge() if (useBufferAge != "0") setSupportsBufferAge(true); } + + setSupportsPartialUpdate(hasExtension(QByteArrayLiteral("EGL_KHR_partial_update"))); + setSupportsSwapBuffersWithDamage(hasExtension(QByteArrayLiteral("EGL_EXT_swap_buffers_with_damage"))); } void AbstractEglBackend::initWayland() diff --git a/platformsupport/scenes/opengl/backend.cpp b/platformsupport/scenes/opengl/backend.cpp index 4ce83b4510..46a68a380e 100644 --- a/platformsupport/scenes/opengl/backend.cpp +++ b/platformsupport/scenes/opengl/backend.cpp @@ -111,4 +111,9 @@ QSharedPointer OpenGLBackend::textureForOutput(AbstractOutput* return {}; } +void OpenGLBackend::aboutToStartPainting(const QRegion &damage) +{ + Q_UNUSED(damage) +} + } diff --git a/platformsupport/scenes/opengl/backend.h b/platformsupport/scenes/opengl/backend.h index 0a76e6e794..53f3143275 100644 --- a/platformsupport/scenes/opengl/backend.h +++ b/platformsupport/scenes/opengl/backend.h @@ -65,6 +65,13 @@ public: */ virtual QRegion prepareRenderingFrame() = 0; + /** + * Notifies about starting to paint. + * + * @p damage contains the reported damage as suggested by windows and effects on prepaint calls. + */ + virtual void aboutToStartPainting(const QRegion &damage); + /** * @brief Backend specific code to handle the end of rendering a frame. * @@ -150,6 +157,15 @@ public: return m_haveBufferAge; } + bool supportsPartialUpdate() const + { + return m_havePartialUpdate; + } + bool supportsSwapBuffersWithDamage() const + { + return m_haveSwapBuffersWithDamage; + } + /** * @returns whether the context is surfaceless */ @@ -241,6 +257,16 @@ protected: m_haveBufferAge = value; } + void setSupportsPartialUpdate(bool value) + { + m_havePartialUpdate = value; + } + + void setSupportsSwapBuffersWithDamage(bool value) + { + m_haveSwapBuffersWithDamage = value; + } + /** * @return const QRegion& Damage of previously rendered frame */ @@ -292,6 +318,11 @@ private: * @brief Whether the backend supports GLX_EXT_buffer_age / EGL_EXT_buffer_age. */ bool m_haveBufferAge; + /** + * @brief Whether the backend supports EGL_KHR_partial_update + */ + bool m_havePartialUpdate; + bool m_haveSwapBuffersWithDamage = false; /** * @brief Whether the initialization failed, of course default to @c false. */ diff --git a/plugins/platforms/drm/egl_gbm_backend.cpp b/plugins/platforms/drm/egl_gbm_backend.cpp index ee827dd80b..68c59af91b 100644 --- a/plugins/platforms/drm/egl_gbm_backend.cpp +++ b/plugins/platforms/drm/egl_gbm_backend.cpp @@ -417,9 +417,54 @@ void EglGbmBackend::present() // Not in use. This backend does per-screen rendering. } +static QVector regionToRects(const QRegion ®ion, AbstractWaylandOutput *output) +{ + const int height = output->modeSize().height(); + + const QMatrix4x4 matrix = output->transformation(); + + QVector rects; + rects.reserve(region.rectCount() * 4); + for (const QRect &_rect : region) { + const QRect rect = matrix.mapRect(_rect); + + rects << rect.left(); + rects << height - (rect.y() + rect.height()); + rects << rect.width(); + rects << rect.height(); + } + return rects; +} + +void EglGbmBackend::aboutToStartPainting(const QRegion &damagedRegion) +{ + // See EglGbmBackend::endRenderingFrameForScreen comment for the reason why we only support screenId=0 + if (m_outputs.count() > 1) { + return; + } + + const Output &output = m_outputs.at(0); + if (output.bufferAge > 0 && !damagedRegion.isEmpty() && supportsPartialUpdate()) { + const QRegion region = damagedRegion & output.output->geometry(); + + QVector rects = regionToRects(region, output.output); + const bool correct = eglSetDamageRegionKHR(eglDisplay(), output.eglSurface, + rects.data(), rects.count()/4); + if (!correct) { + qCWarning(KWIN_DRM) << "failed eglSetDamageRegionKHR" << eglGetError(); + } + } +} + void EglGbmBackend::presentOnOutput(Output &output, const QRegion &damagedRegion) { - eglSwapBuffers(eglDisplay(), output.eglSurface); + if (supportsSwapBuffersWithDamage()) { + QVector rects = regionToRects(output.damageHistory.constFirst(), output.output); + eglSwapBuffersWithDamageEXT(eglDisplay(), output.eglSurface, + rects.data(), rects.count()/4); + } else { + eglSwapBuffers(eglDisplay(), output.eglSurface); + } output.buffer = m_backend->createBuffer(output.gbmSurface); Q_EMIT output.output->outputChange(damagedRegion); diff --git a/plugins/platforms/drm/egl_gbm_backend.h b/plugins/platforms/drm/egl_gbm_backend.h index fbba99e814..b4d991aade 100644 --- a/plugins/platforms/drm/egl_gbm_backend.h +++ b/plugins/platforms/drm/egl_gbm_backend.h @@ -47,6 +47,7 @@ public: protected: void present() override; void cleanupSurfaces() override; + void aboutToStartPainting(const QRegion &damage) override; private: bool initializeEgl(); diff --git a/plugins/platforms/wayland/egl_wayland_backend.cpp b/plugins/platforms/wayland/egl_wayland_backend.cpp index 583e532984..85f90de9f2 100644 --- a/plugins/platforms/wayland/egl_wayland_backend.cpp +++ b/plugins/platforms/wayland/egl_wayland_backend.cpp @@ -286,6 +286,39 @@ void EglWaylandBackend::present() } } +static QVector regionToRects(const QRegion ®ion, AbstractWaylandOutput *output) +{ + const int height = output->modeSize().height(); + const QMatrix4x4 matrix = output->transformation(); + + QVector rects; + rects.reserve(region.rectCount() * 4); + for (const QRect &_rect : region) { + const QRect rect = matrix.mapRect(_rect); + + rects << rect.left(); + rects << height - (rect.y() + rect.height()); + rects << rect.width(); + rects << rect.height(); + } + return rects; +} + +void EglWaylandBackend::aboutToStartPainting(const QRegion &damagedRegion) +{ + EglWaylandOutput* output = m_outputs.at(0); + if (output->m_bufferAge > 0 && !damagedRegion.isEmpty() && supportsPartialUpdate()) { + const QRegion region = damagedRegion & output->m_waylandOutput->geometry(); + + QVector rects = regionToRects(region, output->m_waylandOutput); + const bool correct = eglSetDamageRegionKHR(eglDisplay(), output->m_eglSurface, + rects.data(), rects.count()/4); + if (!correct) { + qCWarning(KWIN_WAYLAND_BACKEND) << "failed eglSetDamageRegionKHR" << eglGetError(); + } + } +} + void EglWaylandBackend::presentOnSurface(EglWaylandOutput *output, const QRegion &damage) { output->m_waylandOutput->surface()->setupFrameCallback(); @@ -296,13 +329,18 @@ void EglWaylandBackend::presentOnSurface(EglWaylandOutput *output, const QRegion Q_EMIT output->m_waylandOutput->outputChange(damage); - if (supportsBufferAge()) { - eglSwapBuffers(eglDisplay(), output->m_eglSurface); - eglQuerySurface(eglDisplay(), output->m_eglSurface, EGL_BUFFER_AGE_EXT, &output->m_bufferAge); + if (supportsSwapBuffersWithDamage() && !output->m_damageHistory.isEmpty()) { + QVector rects = regionToRects(output->m_damageHistory.constFirst(), output->m_waylandOutput); + eglSwapBuffersWithDamageEXT(eglDisplay(), output->m_eglSurface, + rects.data(), rects.count()/4); } else { eglSwapBuffers(eglDisplay(), output->m_eglSurface); } + if (supportsBufferAge()) { + eglQuerySurface(eglDisplay(), output->m_eglSurface, EGL_BUFFER_AGE_EXT, &output->m_bufferAge); + } + } void EglWaylandBackend::screenGeometryChanged(const QSize &size) diff --git a/plugins/platforms/wayland/egl_wayland_backend.h b/plugins/platforms/wayland/egl_wayland_backend.h index 3334df2097..9a82915a97 100644 --- a/plugins/platforms/wayland/egl_wayland_backend.h +++ b/plugins/platforms/wayland/egl_wayland_backend.h @@ -82,6 +82,8 @@ public: return m_havePlatformBase; } + void aboutToStartPainting(const QRegion &damage) override; + private: bool initializeEgl(); bool initBufferConfigs(); diff --git a/plugins/scenes/opengl/scene_opengl.cpp b/plugins/scenes/opengl/scene_opengl.cpp index dec9a85243..7dd1e36ce4 100644 --- a/plugins/scenes/opengl/scene_opengl.cpp +++ b/plugins/scenes/opengl/scene_opengl.cpp @@ -606,6 +606,11 @@ void SceneOpenGL2::paintCursor() glDisable(GL_BLEND); } +void SceneOpenGL::aboutToStartPainting(const QRegion &damage) +{ + m_backend->aboutToStartPainting(damage); +} + qint64 SceneOpenGL::paint(const QRegion &damage, const QList &toplevels) { // actually paint the frame, flushed with the NEXT frame diff --git a/plugins/scenes/opengl/scene_opengl.h b/plugins/scenes/opengl/scene_opengl.h index 9e274dbd1d..98c683226f 100644 --- a/plugins/scenes/opengl/scene_opengl.h +++ b/plugins/scenes/opengl/scene_opengl.h @@ -76,6 +76,7 @@ public: protected: SceneOpenGL(OpenGLBackend *backend, QObject *parent = nullptr); void paintBackground(const QRegion ®ion) override; + void aboutToStartPainting(const QRegion &damage) override; void extendPaintRegion(QRegion ®ion, bool opaqueFullscreen) override; QMatrix4x4 transformation(int mask, const ScreenPaintData &data) const; void paintDesktop(int desktop, int mask, const QRegion ®ion, ScreenPaintData &data) override; diff --git a/scene.cpp b/scene.cpp index 62dd2a73ee..e094efc3ff 100644 --- a/scene.cpp +++ b/scene.cpp @@ -131,10 +131,6 @@ void Scene::paintScreen(int* mask, const QRegion &damage, const QRegion &repaint painted_region = region; repaint_region = repaint; - if (*mask & PAINT_SCREEN_BACKGROUND_FIRST) { - paintBackground(region); - } - ScreenPaintData data(projection, outputGeometry, screenScale); effects->paintScreen(*mask, region, data); @@ -151,6 +147,8 @@ void Scene::paintScreen(int* mask, const QRegion &damage, const QRegion &repaint repaint_region = QRegion(); damaged_region = QRegion(); + m_paintScreenCount = 0; + // make sure all clipping is restored Q_ASSERT(!PaintClipper::clip()); } @@ -183,6 +181,7 @@ void Scene::idle() // the function that'll be eventually called by paintScreen() above void Scene::finalPaintScreen(int mask, const QRegion ®ion, ScreenPaintData& data) { + m_paintScreenCount++; if (mask & (PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS)) paintGenericScreen(mask, data); else @@ -195,9 +194,6 @@ void Scene::finalPaintScreen(int mask, const QRegion ®ion, ScreenPaintData& d // It simply paints bottom-to-top. void Scene::paintGenericScreen(int orig_mask, const ScreenPaintData &) { - if (!(orig_mask & PAINT_SCREEN_BACKGROUND_FIRST)) { - paintBackground(infiniteRegion()); - } QVector phase2; phase2.reserve(stacking_order.size()); foreach (Window * w, stacking_order) { // bottom to top @@ -230,12 +226,21 @@ void Scene::paintGenericScreen(int orig_mask, const ScreenPaintData &) phase2.append({w, infiniteRegion(), data.clip, data.mask, data.quads}); } + damaged_region = QRegion(QRect {{}, screens()->size()}); + if (m_paintScreenCount == 1) { + aboutToStartPainting(damaged_region); + + if (orig_mask & PAINT_SCREEN_BACKGROUND_FIRST) { + paintBackground(infiniteRegion()); + } + } + + if (!(orig_mask & PAINT_SCREEN_BACKGROUND_FIRST)) { + paintBackground(infiniteRegion()); + } foreach (const Phase2Data & d, phase2) { paintWindow(d.window, d.mask, d.region, d.quads); } - - const QSize &screenSize = screens()->size(); - damaged_region = QRegion(0, 0, screenSize.width(), screenSize.height()); } // The optimized case without any transformations at all. @@ -350,6 +355,13 @@ void Scene::paintSimpleScreen(int orig_mask, const QRegion ®ion) QRegion paintedArea; // Fill any areas of the root window not covered by opaque windows + if (m_paintScreenCount == 1) { + aboutToStartPainting(dirtyArea); + + if (orig_mask & PAINT_SCREEN_BACKGROUND_FIRST) { + paintBackground(infiniteRegion()); + } + } if (!(orig_mask & PAINT_SCREEN_BACKGROUND_FIRST)) { paintedArea = dirtyArea - allclips; paintBackground(paintedArea); @@ -603,6 +615,11 @@ void Scene::paintDesktop(int desktop, int mask, const QRegion ®ion, ScreenPai static_cast(effects)->paintDesktop(desktop, mask, region, data); } +void Scene::aboutToStartPainting(const QRegion &damage) +{ + Q_UNUSED(damage) +} + // the function that'll be eventually called by paintWindow() above void Scene::finalPaintWindow(EffectWindowImpl* w, int mask, const QRegion ®ion, WindowPaintData& data) { diff --git a/scene.h b/scene.h index 7dcb35dcbd..af1bdbc67f 100644 --- a/scene.h +++ b/scene.h @@ -217,6 +217,13 @@ protected: virtual void paintSimpleScreen(int mask, const QRegion ®ion); // paint the background (not the desktop background - the whole background) virtual void paintBackground(const QRegion ®ion) = 0; + + /** + * Notifies about starting to paint. + * + * @p damage contains the reported damage as suggested by windows and effects on prepaint calls. + */ + virtual void aboutToStartPainting(const QRegion &damage); // called after all effects had their paintWindow() called void finalPaintWindow(EffectWindowImpl* w, int mask, const QRegion ®ion, WindowPaintData& data); // shared implementation, starts painting the window @@ -259,6 +266,8 @@ private: QHash< Toplevel*, Window* > m_windows; // windows in their stacking order QVector< Window* > stacking_order; + // how many times finalPaintScreen() has been called + int m_paintScreenCount = 0; }; /**