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

View File

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