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.
master
Aleix Pol 2020-04-24 19:11:41 +02:00 committed by Aleix Pol Gonzalez
parent d5da366507
commit eeeac04974
11 changed files with 171 additions and 14 deletions

View File

@ -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()

View File

@ -111,4 +111,9 @@ QSharedPointer<KWin::GLTexture> OpenGLBackend::textureForOutput(AbstractOutput*
return {};
}
void OpenGLBackend::aboutToStartPainting(const QRegion &damage)
{
Q_UNUSED(damage)
}
}

View File

@ -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.
*/

View File

@ -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);
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);

View File

@ -47,6 +47,7 @@ public:
protected:
void present() override;
void cleanupSurfaces() override;
void aboutToStartPainting(const QRegion &damage) override;
private:
bool initializeEgl();

View File

@ -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)

View File

@ -82,6 +82,8 @@ public:
return m_havePlatformBase;
}
void aboutToStartPainting(const QRegion &damage) override;
private:
bool initializeEgl();
bool initBufferConfigs();

View File

@ -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

View File

@ -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;

View File

@ -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)
{

View File

@ -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;
};
/**