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.
This commit is contained in:
parent
d5da366507
commit
eeeac04974
11 changed files with 171 additions and 14 deletions
|
@ -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 ®ion, 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);
|
||||
|
|
|
@ -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 ®ion, 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 ®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;
|
||||
|
|
37
scene.cpp
37
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<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 ®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<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 ®ion, WindowPaintData& data)
|
||||
{
|
||||
|
|
9
scene.h
9
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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue