diff --git a/platformsupport/scenes/opengl/abstract_egl_backend.cpp b/platformsupport/scenes/opengl/abstract_egl_backend.cpp --- a/platformsupport/scenes/opengl/abstract_egl_backend.cpp +++ b/platformsupport/scenes/opengl/abstract_egl_backend.cpp @@ -152,6 +152,9 @@ 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.h b/platformsupport/scenes/opengl/backend.h --- a/platformsupport/scenes/opengl/backend.h +++ b/platformsupport/scenes/opengl/backend.h @@ -74,6 +74,13 @@ */ 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. * @@ -159,6 +166,15 @@ return m_haveBufferAge; } + bool supportsPartialUpdate() const + { + return m_havePartialUpdate; + } + bool supportsSwapBuffersWithDamage() const + { + return m_haveSwapBuffersWithDamage; + } + /** * @returns whether the context is surfaceless */ @@ -248,6 +264,16 @@ 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 */ @@ -299,6 +325,11 @@ * @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/platformsupport/scenes/opengl/backend.cpp b/platformsupport/scenes/opengl/backend.cpp --- a/platformsupport/scenes/opengl/backend.cpp +++ b/platformsupport/scenes/opengl/backend.cpp @@ -116,4 +116,8 @@ } } +void OpenGLBackend::aboutToStartPainting(const QRegion &damage) +{ + Q_UNUSED(damage); +} } diff --git a/plugins/platforms/drm/egl_gbm_backend.h b/plugins/platforms/drm/egl_gbm_backend.h --- a/plugins/platforms/drm/egl_gbm_backend.h +++ b/plugins/platforms/drm/egl_gbm_backend.h @@ -55,6 +55,7 @@ protected: void present() override; void cleanupSurfaces() override; + void aboutToStartPainting(const QRegion &damage) override; private: bool initializeEgl(); diff --git a/plugins/platforms/drm/egl_gbm_backend.cpp b/plugins/platforms/drm/egl_gbm_backend.cpp --- a/plugins/platforms/drm/egl_gbm_backend.cpp +++ b/plugins/platforms/drm/egl_gbm_backend.cpp @@ -438,9 +438,62 @@ // Not in use. This backend does per-screen rendering. } +static QVector regionToRects(const QRegion ®ion, DrmOutput* output) +{ + const int height = output->pixelSize().height(); + QMatrix4x4 matrix; + matrix.scale(output->scale()); + matrix.rotate(output->rotation(), 0, 0, 1); + + const QPoint topLeft = -output->geometry().topLeft(); + matrix.translate(-topLeft.x(), -topLeft.y()); + + QVector rects; + rects.reserve(region.rectCount() * 4); + for (const QRect &_rect : region) { + 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) { - 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); if(m_remoteaccessManager && gbm_surface_has_free_buffers(output.gbmSurface->surface())) { diff --git a/plugins/scenes/opengl/scene_opengl.h b/plugins/scenes/opengl/scene_opengl.h --- a/plugins/scenes/opengl/scene_opengl.h +++ b/plugins/scenes/opengl/scene_opengl.h @@ -86,6 +86,7 @@ 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/plugins/scenes/opengl/scene_opengl.cpp b/plugins/scenes/opengl/scene_opengl.cpp --- a/plugins/scenes/opengl/scene_opengl.cpp +++ b/plugins/scenes/opengl/scene_opengl.cpp @@ -616,6 +616,11 @@ 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/scene.h b/scene.h --- a/scene.h +++ b/scene.h @@ -226,6 +226,13 @@ 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 @@ -268,6 +275,8 @@ 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; }; /** diff --git a/scene.cpp b/scene.cpp --- a/scene.cpp +++ b/scene.cpp @@ -141,10 +141,6 @@ painted_region = region; repaint_region = repaint; - if (*mask & PAINT_SCREEN_BACKGROUND_FIRST) { - paintBackground(region); - } - ScreenPaintData data(projection, outputGeometry, screenScale); effects->paintScreen(*mask, region, data); @@ -161,6 +157,8 @@ repaint_region = QRegion(); damaged_region = QRegion(); + m_paintScreenCount = 0; + // make sure all clipping is restored Q_ASSERT(!PaintClipper::clip()); } @@ -193,6 +191,7 @@ // 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 @@ -203,9 +202,6 @@ // 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 @@ -235,12 +231,21 @@ 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. @@ -352,6 +357,13 @@ 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); @@ -599,6 +611,11 @@ 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) {