From eeeac049741f67dd76864c8d00c54211185b08f8 Mon Sep 17 00:00:00 2001 From: Aleix Pol Date: Fri, 24 Apr 2020 19:11:41 +0200 Subject: [PATCH] Implement EGL_KHR_partial_update and EGL_EXT_swap_buffers_with_damage Summary: Notify the driver about the parts of the screen that will be repainted. In some cases this can be benefitial. This is especially useful on lima and panfrost devices (e.g. pinephone, pinebook, pinebook pro). Test Plan: Tested on a pinebook pro with a late mesa version. Basically I implemented it, then it didn't work and I fixed it. Maybe next step we want to look into our damage algorithm. --- .../scenes/opengl/abstract_egl_backend.cpp | 3 ++ platformsupport/scenes/opengl/backend.cpp | 5 ++ platformsupport/scenes/opengl/backend.h | 31 ++++++++++++ plugins/platforms/drm/egl_gbm_backend.cpp | 47 ++++++++++++++++++- plugins/platforms/drm/egl_gbm_backend.h | 1 + .../platforms/wayland/egl_wayland_backend.cpp | 44 +++++++++++++++-- .../platforms/wayland/egl_wayland_backend.h | 2 + plugins/scenes/opengl/scene_opengl.cpp | 5 ++ plugins/scenes/opengl/scene_opengl.h | 1 + scene.cpp | 37 +++++++++++---- scene.h | 9 ++++ 11 files changed, 171 insertions(+), 14 deletions(-) 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; }; /**