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, QWaylandShmBuffer::QWaylandShmBuffer(QWaylandDisplay *display,
const QSize &size, QImage::Format format, qreal scale) const QSize &size, QImage::Format format, qreal scale)
: mDirtyRegion(QRect(QPoint(0, 0), size / scale))
{ {
int stride = size.width() * 4; int stride = size.width() * 4;
int alloc = stride * size.height(); int alloc = stride * size.height();
@ -166,11 +167,24 @@ QPaintDevice *QWaylandShmBackingStore::paintDevice()
return contentSurface(); 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) void QWaylandShmBackingStore::beginPaint(const QRegion &region)
{ {
mPainting = true; mPainting = true;
ensureSize(); ensureSize();
const QMargins margins = windowDecorationMargins();
updateDirtyStates(region.translated(margins.left(), margins.top()));
if (mBackBuffer->image()->hasAlphaChannel()) { if (mBackBuffer->image()->hasAlphaChannel()) {
QPainter p(paintDevice()); QPainter p(paintDevice());
p.setCompositionMode(QPainter::CompositionMode_Source); 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; 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, // 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 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 // buffer has a different size it needs to be redrawn completely anyway, and if the buffer
// is the same the stuff is there already. // is the same the stuff is there already.
@ -282,8 +296,27 @@ void QWaylandShmBackingStore::resize(const QSize &size)
qsizetype newSizeInBytes = buffer->image()->sizeInBytes(); 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 // 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) if (mBackBuffer != buffer && oldSizeInBytes == newSizeInBytes) {
memcpy(buffer->image()->bits(), mBackBuffer->image()->constBits(), 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; mBackBuffer = buffer;
@ -296,6 +329,8 @@ void QWaylandShmBackingStore::resize(const QSize &size)
if (windowDecoration() && window()->isVisible() && oldSizeInBytes != newSizeInBytes) if (windowDecoration() && window()->isVisible() && oldSizeInBytes != newSizeInBytes)
windowDecoration()->update(); windowDecoration()->update();
buffer->dirtyRegion() = QRegion();
} }
QImage *QWaylandShmBackingStore::entireSurface() const QImage *QWaylandShmBackingStore::entireSurface() const
@ -320,6 +355,7 @@ void QWaylandShmBackingStore::updateDecorations()
QTransform sourceMatrix; QTransform sourceMatrix;
sourceMatrix.scale(dp, dp); sourceMatrix.scale(dp, dp);
QRect target; // needs to be in device independent pixels QRect target; // needs to be in device independent pixels
QRegion dirtyRegion;
//Top //Top
target.setX(0); target.setX(0);
@ -327,16 +363,19 @@ void QWaylandShmBackingStore::updateDecorations()
target.setWidth(dpWidth); target.setWidth(dpWidth);
target.setHeight(windowDecorationMargins().top()); target.setHeight(windowDecorationMargins().top());
decorationPainter.drawImage(target, sourceImage, sourceMatrix.mapRect(target)); decorationPainter.drawImage(target, sourceImage, sourceMatrix.mapRect(target));
dirtyRegion += target;
//Left //Left
target.setWidth(windowDecorationMargins().left()); target.setWidth(windowDecorationMargins().left());
target.setHeight(dpHeight); target.setHeight(dpHeight);
decorationPainter.drawImage(target, sourceImage, sourceMatrix.mapRect(target)); decorationPainter.drawImage(target, sourceImage, sourceMatrix.mapRect(target));
dirtyRegion += target;
//Right //Right
target.setX(dpWidth - windowDecorationMargins().right()); target.setX(dpWidth - windowDecorationMargins().right());
target.setWidth(windowDecorationMargins().right()); target.setWidth(windowDecorationMargins().right());
decorationPainter.drawImage(target, sourceImage, sourceMatrix.mapRect(target)); decorationPainter.drawImage(target, sourceImage, sourceMatrix.mapRect(target));
dirtyRegion += target;
//Bottom //Bottom
target.setX(0); target.setX(0);
@ -344,6 +383,9 @@ void QWaylandShmBackingStore::updateDecorations()
target.setWidth(dpWidth); target.setWidth(dpWidth);
target.setHeight(windowDecorationMargins().bottom()); target.setHeight(windowDecorationMargins().bottom());
decorationPainter.drawImage(target, sourceImage, sourceMatrix.mapRect(target)); decorationPainter.drawImage(target, sourceImage, sourceMatrix.mapRect(target));
dirtyRegion += target;
updateDirtyStates(dirtyRegion);
} }
QWaylandAbstractDecoration *QWaylandShmBackingStore::windowDecoration() const QWaylandAbstractDecoration *QWaylandShmBackingStore::windowDecoration() const

View File

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