Commit eeeac049 authored by Aleix Pol Gonzalez's avatar Aleix Pol Gonzalez 🐧 Committed by Aleix Pol Gonzalez
Browse files

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.
parent d5da3665
......@@ -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()
......
......@@ -111,4 +111,9 @@ QSharedPointer<KWin::GLTexture> OpenGLBackend::textureForOutput(AbstractOutput*
return {};
}
void OpenGLBackend::aboutToStartPainting(const QRegion &damage)
{
Q_UNUSED(damage)
}
}
......@@ -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.
*/
......
......@@ -417,9 +417,54 @@ void EglGbmBackend::present()
// Not in use. This backend does per-screen rendering.
}
static QVector<EGLint> regionToRects(const QRegion &region, AbstractWaylandOutput *output)
{
const int height = output->modeSize().height();
const QMatrix4x4 matrix = output->transformation();
QVector<EGLint> 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<EGLint> 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<EGLint> rects = regionToRects(output.damageHistory.constFirst(), output.output);
  • For the very first frame the damageHistory is empty. This is not checked and causes a crash.

    #0  QRegion::rectCount (this=this@entry=0x0) at painting/qregion.cpp:4375
    #1  0x0000ffffb1121ebc in KWin::regionToRects (region=..., output=0xaaaac9713240) at ./plugins/platforms/drm/egl_gbm_backend.cpp:458
    #2  0x0000ffffb11223b8 in KWin::EglGbmBackend::presentOnOutput (this=0xaaaac974af40, output=..., damagedRegion=...) at ./plugins/platforms/drm/egl_gbm_backend.cpp:493
    #3  0x0000ffffb1122d98 in KWin::EglGbmBackend::endRenderingFrameForScreen (this=0xaaaac974af40, screenId=0, renderedRegion=..., damagedRegion=...) at ./plugins/platforms/drm/egl_gbm_backend.cpp:590
    #4  0x0000ffffb00c1348 in KWin::SceneOpenGL::paint(QRegion const&, QList<KWin::Toplevel*> const&) () from /usr/lib/aarch64-linux-gnu/qt5/plugins/org.kde.kwin.scenes/KWinSceneOpenGL.so
    #5  0x0000ffffb9ca5b84 in KWin::Compositor::performCompositing() () from /lib/aarch64-linux-gnu/libkwin.so.5 
    #6  0x0000ffffb84dd7d4 in QObject::event(QEvent*) () from /lib/aarch64-linux-gnu/libQt5Core.so.5 
    #7  0x0000ffffb8f4da80 in QApplicationPrivate::notify_helper(QObject*, QEvent*) () from /lib/aarch64-linux-gnu/libQt5Widgets.so.5 
    #8  0x0000ffffb84ab460 in QCoreApplication::notifyInternal2(QObject*, QEvent*) () from /lib/aarch64-linux-gnu/libQt5Core.so.5 
    #9  0x0000ffffb84ae338 in QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*) () from /lib/aarch64-linux-gnu/libQt5Core.so.5 
    #10 0x0000ffffb850a374 in QEventDispatcherUNIX::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) () from /lib/aarch64-linux-gnu/libQt5Core.so.5 
    #11 0x0000ffffb1198814 in QUnixEventDispatcherQPA::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) () from /usr/lib/aarch64-linux-gnu/qt5/plugins/platforms/KWinQpaPlugin.so 
    #12 0x0000ffffb84a98ac in QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) () from /lib/aarch64-linux-gnu/libQt5Core.so.5 
    #13 0x0000ffffb84b2a7c in QCoreApplication::exec() () from /lib/aarch64-linux-gnu/libQt5Core.so.5 
    #14 0x0000aaaabe164dc0 in main (argc=<optimized out>, argv=<optimized out>) at ./main_wayland.cpp:702

    From reading the code the damageHistory is filled in endRenderingFrameForScreen after the call to presentOnOutput.

    Experiencing this crash on 5.20.5 with proprietary Vivante driver.

  • we probably don't even need to use damageHistory. damagedRegion should be good enough.

Please register or sign in to reply
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);
......
......@@ -47,6 +47,7 @@ public:
protected:
void present() override;
void cleanupSurfaces() override;
void aboutToStartPainting(const QRegion &damage) override;
private:
bool initializeEgl();
......
......@@ -286,6 +286,39 @@ void EglWaylandBackend::present()
}
}
static QVector<EGLint> regionToRects(const QRegion &region, AbstractWaylandOutput *output)
{
const int height = output->modeSize().height();
const QMatrix4x4 matrix = output->transformation();
QVector<EGLint> 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<EGLint> 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<EGLint> 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)
......
......@@ -82,6 +82,8 @@ public:
return m_havePlatformBase;
}
void aboutToStartPainting(const QRegion &damage) override;
private:
bool initializeEgl();
bool initBufferConfigs();
......
......@@ -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<Toplevel *> &toplevels)
{
// actually paint the frame, flushed with the NEXT frame
......
......@@ -76,6 +76,7 @@ public:
protected:
SceneOpenGL(OpenGLBackend *backend, QObject *parent = nullptr);
void paintBackground(const QRegion &region) override;
void aboutToStartPainting(const QRegion &damage) override;
void extendPaintRegion(QRegion &region, bool opaqueFullscreen) override;
QMatrix4x4 transformation(int mask, const ScreenPaintData &data) const;
void paintDesktop(int desktop, int mask, const QRegion &region, ScreenPaintData &data) override;
......
......@@ -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 &region, 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 &region, 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<Phase2Data> 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 &region)
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 &region, ScreenPai
static_cast<EffectsHandlerImpl*>(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 &region, WindowPaintData& data)
{
......
......@@ -217,6 +217,13 @@ protected:
virtual void paintSimpleScreen(int mask, const QRegion &region);
// paint the background (not the desktop background - the whole background)
virtual void paintBackground(const QRegion &region) = 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 &region, 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;
};
/**
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment