QWaylandShmBackingStore: Preserve buffer contents between frames

Doing a memcpy is quite expensive, particularly when only a small
region of the buffer (such as a blinking cursor) actually changed.

Instead, keep track of the damaged region and paint only what
actually changed into the buffer to be used.

Change-Id: Ibd81bbfe20d0750ddb751f41722a316387225ba6
Reviewed-by: David Edmundson <davidedmundson@kde.org>
This commit is contained in:
Kai Uwe Broulik 2024-01-01 18:14:39 +01:00
parent 68ddc184fd
commit c928425852
2 changed files with 49 additions and 3 deletions

View File

@ -47,6 +47,7 @@ namespace QtWaylandClient {
QWaylandShmBuffer::QWaylandShmBuffer(QWaylandDisplay *display,
const QSize &size, QImage::Format format, qreal scale)
: mDirtyRegion(QRect(QPoint(0, 0), size / scale))
{
int stride = size.width() * 4;
int alloc = stride * size.height();
@ -166,11 +167,24 @@ QPaintDevice *QWaylandShmBackingStore::paintDevice()
return contentSurface();
}
void QWaylandShmBackingStore::updateDirtyStates(const QRegion &region)
{
// Update dirty state of buffers based on what was painted. The back buffer will
// not be dirty since we already painted on it, while other buffers will become dirty.
for (QWaylandShmBuffer *b : std::as_const(mBuffers)) {
if (b != mBackBuffer)
b->dirtyRegion() += region;
}
}
void QWaylandShmBackingStore::beginPaint(const QRegion &region)
{
mPainting = true;
ensureSize();
const QMargins margins = windowDecorationMargins();
updateDirtyStates(region.translated(margins.left(), margins.top()));
if (mBackBuffer->image()->hasAlphaChannel()) {
QPainter p(paintDevice());
p.setCompositionMode(QPainter::CompositionMode_Source);
@ -263,7 +277,7 @@ void QWaylandShmBackingStore::resize(const QSize &size)
QSize sizeWithMargins = (size + QSize(margins.left()+margins.right(),margins.top()+margins.bottom())) * scale;
// We look for a free buffer to draw into. If the buffer is not the last buffer we used,
// that is mBackBuffer, and the size is the same we memcpy the old content into the new
// that is mBackBuffer, and the size is the same we copy the damaged content into the new
// buffer so that QPainter is happy to find the stuff it had drawn before. If the new
// buffer has a different size it needs to be redrawn completely anyway, and if the buffer
// is the same the stuff is there already.
@ -282,8 +296,27 @@ void QWaylandShmBackingStore::resize(const QSize &size)
qsizetype newSizeInBytes = buffer->image()->sizeInBytes();
// mBackBuffer may have been deleted here but if so it means its size was different so we wouldn't copy it anyway
if (mBackBuffer != buffer && oldSizeInBytes == newSizeInBytes)
memcpy(buffer->image()->bits(), mBackBuffer->image()->constBits(), newSizeInBytes);
if (mBackBuffer != buffer && oldSizeInBytes == newSizeInBytes) {
Q_ASSERT(mBackBuffer);
const QImage *sourceImage = mBackBuffer->image();
QImage *targetImage = buffer->image();
QPainter painter(targetImage);
painter.setCompositionMode(QPainter::CompositionMode_Source);
// Let painter operate in device pixels, to make it easier to compare coordinates
const qreal sourceDevicePixelRatio = sourceImage->devicePixelRatio();
const qreal targetDevicePixelRatio = painter.device()->devicePixelRatio();
painter.scale(1.0 / targetDevicePixelRatio, 1.0 / targetDevicePixelRatio);
for (const QRect &rect : buffer->dirtyRegion()) {
QRectF sourceRect(QPointF(rect.topLeft()) * sourceDevicePixelRatio,
QSizeF(rect.size()) * sourceDevicePixelRatio);
QRectF targetRect(QPointF(rect.topLeft()) * targetDevicePixelRatio,
QSizeF(rect.size()) * targetDevicePixelRatio);
painter.drawImage(targetRect, *sourceImage, sourceRect);
}
}
mBackBuffer = buffer;
@ -296,6 +329,8 @@ void QWaylandShmBackingStore::resize(const QSize &size)
if (windowDecoration() && window()->isVisible() && oldSizeInBytes != newSizeInBytes)
windowDecoration()->update();
buffer->dirtyRegion() = QRegion();
}
QImage *QWaylandShmBackingStore::entireSurface() const
@ -320,6 +355,7 @@ void QWaylandShmBackingStore::updateDecorations()
QTransform sourceMatrix;
sourceMatrix.scale(dp, dp);
QRect target; // needs to be in device independent pixels
QRegion dirtyRegion;
//Top
target.setX(0);
@ -327,16 +363,19 @@ void QWaylandShmBackingStore::updateDecorations()
target.setWidth(dpWidth);
target.setHeight(windowDecorationMargins().top());
decorationPainter.drawImage(target, sourceImage, sourceMatrix.mapRect(target));
dirtyRegion += target;
//Left
target.setWidth(windowDecorationMargins().left());
target.setHeight(dpHeight);
decorationPainter.drawImage(target, sourceImage, sourceMatrix.mapRect(target));
dirtyRegion += target;
//Right
target.setX(dpWidth - windowDecorationMargins().right());
target.setWidth(windowDecorationMargins().right());
decorationPainter.drawImage(target, sourceImage, sourceMatrix.mapRect(target));
dirtyRegion += target;
//Bottom
target.setX(0);
@ -344,6 +383,9 @@ void QWaylandShmBackingStore::updateDecorations()
target.setWidth(dpWidth);
target.setHeight(windowDecorationMargins().bottom());
decorationPainter.drawImage(target, sourceImage, sourceMatrix.mapRect(target));
dirtyRegion += target;
updateDirtyStates(dirtyRegion);
}
QWaylandAbstractDecoration *QWaylandShmBackingStore::windowDecoration() const

View File

@ -42,11 +42,14 @@ public:
QImage *image() { return &mImage; }
QImage *imageInsideMargins(const QMargins &margins);
QRegion &dirtyRegion() { return mDirtyRegion; }
private:
QImage mImage;
struct wl_shm_pool *mShmPool = nullptr;
QMargins mMargins;
QImage *mMarginsImage = nullptr;
QRegion mDirtyRegion;
};
class Q_WAYLANDCLIENT_EXPORT QWaylandShmBackingStore : public QPlatformBackingStore
@ -77,6 +80,7 @@ public:
#endif
private:
void updateDirtyStates(const QRegion &region);
void updateDecorations();
QWaylandShmBuffer *getBuffer(const QSize &size);