Client: Prune stale buffers in QWaylandShmBackingStore

A backing store may sometimes allocate an extra buffer and keep it even
though the buffer is never touched again.

This change makes the QWaylandShmBackingStore clean up the old buffers
more aggressively.

Specifically,

* if a buffer is referenced by the compositor and has a different size,
  there is no need to wait for the buffer to be released, it can be
  destroyed immediately
* if a buffer has not been used for the past 50 frames, it probably
  won't be used any time soon so it can be disposed

Depending on the compositor implementation details, the
QWaylandShmBackingStore is likely to hold either one or two buffers
now. Note that the backing store can still sometimes have more buffers
because Qt widgets not always use QWindow::requestUpdate().

Change-Id: I57428d457d889dd07c4beb641cf380b1aceb88c2
Reviewed-by: David Edmundson <davidedmundson@kde.org>
This commit is contained in:
Vlad Zahorodnii 2025-01-02 03:56:43 +02:00
parent 412b1ecd5c
commit a99d09f0b3
2 changed files with 34 additions and 20 deletions

View File

@ -255,23 +255,33 @@ void QWaylandShmBackingStore::resize(const QSize &size, const QRegion &)
QWaylandShmBuffer *QWaylandShmBackingStore::getBuffer(const QSize &size, bool &bufferWasRecreated) QWaylandShmBuffer *QWaylandShmBackingStore::getBuffer(const QSize &size, bool &bufferWasRecreated)
{ {
static const int MAX_BUFFERS = 5;
static const int MAX_AGE = 10 * MAX_BUFFERS;
bufferWasRecreated = false; bufferWasRecreated = false;
const auto copy = mBuffers; // remove when ported to vector<unique_ptr> + remove_if // Prune buffers that have not been used in a while or with different size.
for (QWaylandShmBuffer *b : copy) { for (auto i = mBuffers.size() - 1; i >= 0; --i) {
if (!b->busy()) { QWaylandShmBuffer *buffer = mBuffers[i];
if (b->size() == size) { if (buffer->age() > MAX_AGE || buffer->size() != size) {
return b; mBuffers.removeAt(i);
} else { if (mBackBuffer == buffer)
mBuffers.remove(b); mBackBuffer = nullptr;
if (mBackBuffer == b) delete buffer;
mBackBuffer = nullptr;
delete b;
}
} }
} }
static const size_t MAX_BUFFERS = 5; QWaylandShmBuffer *buffer = nullptr;
for (QWaylandShmBuffer *candidate : std::as_const(mBuffers)) {
if (candidate->busy())
continue;
if (!buffer || candidate->age() < buffer->age())
buffer = candidate;
}
if (buffer)
return buffer;
if (mBuffers.size() < MAX_BUFFERS) { if (mBuffers.size() < MAX_BUFFERS) {
QImage::Format format = QPlatformScreen::platformScreenForWindow(window())->format(); QImage::Format format = QPlatformScreen::platformScreenForWindow(window())->format();
QWaylandShmBuffer *b = new QWaylandShmBuffer(mDisplay, size, format, waylandWindow()->scale()); QWaylandShmBuffer *b = new QWaylandShmBuffer(mDisplay, size, format, waylandWindow()->scale());
@ -327,11 +337,12 @@ bool QWaylandShmBackingStore::recreateBackBufferIfNeeded()
mBackBuffer = buffer; mBackBuffer = buffer;
// ensure the new buffer is at the beginning of the list so next time getBuffer() will pick for (QWaylandShmBuffer *buffer : std::as_const(mBuffers)) {
// it if possible if (mBackBuffer == buffer) {
if (mBuffers.front() != buffer) { buffer->setAge(0);
mBuffers.remove(buffer); } else {
mBuffers.push_front(buffer); buffer->setAge(buffer->age() + 1);
}
} }
if (windowDecoration() && window()->isVisible() && oldSizeInBytes != newSizeInBytes) if (windowDecoration() && window()->isVisible() && oldSizeInBytes != newSizeInBytes)

View File

@ -22,8 +22,6 @@
#include <qpa/qplatformwindow.h> #include <qpa/qplatformwindow.h>
#include <QMutex> #include <QMutex>
#include <list>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
namespace QtWaylandClient { namespace QtWaylandClient {
@ -44,12 +42,17 @@ public:
QImage *imageInsideMargins(const QMargins &margins); QImage *imageInsideMargins(const QMargins &margins);
QRegion &dirtyRegion() { return mDirtyRegion; } QRegion &dirtyRegion() { return mDirtyRegion; }
uint age() const { return mAge; }
void setAge(uint age) { mAge = age; }
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; QRegion mDirtyRegion;
uint mAge = 0;
}; };
class Q_WAYLANDCLIENT_EXPORT QWaylandShmBackingStore : public QPlatformBackingStore class Q_WAYLANDCLIENT_EXPORT QWaylandShmBackingStore : public QPlatformBackingStore
@ -84,7 +87,7 @@ private:
QWaylandShmBuffer *getBuffer(const QSize &size, bool &bufferWasRecreated); QWaylandShmBuffer *getBuffer(const QSize &size, bool &bufferWasRecreated);
QWaylandDisplay *mDisplay = nullptr; QWaylandDisplay *mDisplay = nullptr;
std::list<QWaylandShmBuffer *> mBuffers; QList<QWaylandShmBuffer *> mBuffers;
QWaylandShmBuffer *mFrontBuffer = nullptr; QWaylandShmBuffer *mFrontBuffer = nullptr;
QWaylandShmBuffer *mBackBuffer = nullptr; QWaylandShmBuffer *mBackBuffer = nullptr;
bool mPainting = false; bool mPainting = false;