From 20477f135136ecc4d5b4bba172390e658cb01683 Mon Sep 17 00:00:00 2001 From: Kai Uwe Broulik Date: Mon, 1 Jan 2024 18:14:39 +0100 Subject: 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 --- src/client/qwaylandshmbackingstore.cpp | 48 +++++++++++++++++++++++++++++++--- src/client/qwaylandshmbackingstore_p.h | 4 +++ 2 files changed, 49 insertions(+), 3 deletions(-) (limited to 'src/client') diff --git a/src/client/qwaylandshmbackingstore.cpp b/src/client/qwaylandshmbackingstore.cpp index 02d830ad8..ffd17ce89 100644 --- a/src/client/qwaylandshmbackingstore.cpp +++ b/src/client/qwaylandshmbackingstore.cpp @@ -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 ®ion) +{ + // 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 ®ion) { 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 diff --git a/src/client/qwaylandshmbackingstore_p.h b/src/client/qwaylandshmbackingstore_p.h index 8a63b3c14..8c7b83dbb 100644 --- a/src/client/qwaylandshmbackingstore_p.h +++ b/src/client/qwaylandshmbackingstore_p.h @@ -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 ®ion); void updateDecorations(); QWaylandShmBuffer *getBuffer(const QSize &size); -- cgit v1.2.3