diff options
Diffstat (limited to 'src/client/qwaylandshmbackingstore.cpp')
-rw-r--r-- | src/client/qwaylandshmbackingstore.cpp | 114 |
1 files changed, 92 insertions, 22 deletions
diff --git a/src/client/qwaylandshmbackingstore.cpp b/src/client/qwaylandshmbackingstore.cpp index a8e59637a..d77c548a0 100644 --- a/src/client/qwaylandshmbackingstore.cpp +++ b/src/client/qwaylandshmbackingstore.cpp @@ -16,6 +16,7 @@ #include <QtWaylandClient/private/wayland-wayland-client-protocol.h> +#include <fcntl.h> #include <unistd.h> #include <sys/mman.h> @@ -25,6 +26,19 @@ # ifndef MFD_CLOEXEC # define MFD_CLOEXEC 0x0001U # endif +# ifndef MFD_ALLOW_SEALING +# define MFD_ALLOW_SEALING 0x0002U +# endif +// from bits/fcntl-linux.h +# ifndef F_ADD_SEALS +# define F_ADD_SEALS 1033 +# endif +# ifndef F_SEAL_SEAL +# define F_SEAL_SEAL 0x0001 +# endif +# ifndef F_SEAL_SHRINK +# define F_SEAL_SHRINK 0x0002 +# endif #endif QT_BEGIN_NAMESPACE @@ -33,13 +47,16 @@ 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(); int fd = -1; #ifdef SYS_memfd_create - fd = syscall(SYS_memfd_create, "wayland-shm", MFD_CLOEXEC); + fd = syscall(SYS_memfd_create, "wayland-shm", MFD_CLOEXEC | MFD_ALLOW_SEALING); + if (fd >= 0) + fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL); #endif QScopedPointer<QFile> filePointer; @@ -54,6 +71,7 @@ QWaylandShmBuffer::QWaylandShmBuffer(QWaylandDisplay *display, file->open(fd, QIODevice::ReadWrite | QIODevice::Unbuffered, QFile::AutoCloseHandle); filePointer.reset(file); } + // NOTE beginPaint assumes a new buffer be all zeroes, which QFile::resize does. if (!filePointer->isOpen() || !filePointer->resize(alloc)) { qWarning("QWaylandShmBuffer: failed: %s", qUtf8Printable(filePointer->errorString())); return; @@ -90,7 +108,7 @@ QWaylandShmBuffer::~QWaylandShmBuffer(void) QImage *QWaylandShmBuffer::imageInsideMargins(const QMargins &marginsIn) { - QMargins margins = marginsIn * int(mImage.devicePixelRatio()); + QMargins margins = marginsIn * mImage.devicePixelRatio(); if (!margins.isNull() && margins != mMargins) { if (mMarginsImage) { @@ -120,7 +138,18 @@ QWaylandShmBackingStore::QWaylandShmBackingStore(QWindow *window, QWaylandDispla : QPlatformBackingStore(window) , mDisplay(display) { - + QObject::connect(mDisplay, &QWaylandDisplay::connected, window, [this]() { + auto copy = mBuffers; + // clear available buffers so we create new ones + // actual deletion is deferred till after resize call so we can copy + // contents from the back buffer + mBuffers.clear(); + mFrontBuffer = nullptr; + // recreateBackBufferIfNeeded always resets mBackBuffer + if (mRequestedSize.isValid() && waylandWindow()) + recreateBackBufferIfNeeded(); + qDeleteAll(copy); + }); } QWaylandShmBackingStore::~QWaylandShmBackingStore() @@ -139,14 +168,29 @@ 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(); + waylandWindow()->setBackingStore(this); + const bool bufferWasRecreated = recreateBackBufferIfNeeded(); - waylandWindow()->setCanResize(false); + const QMargins margins = windowDecorationMargins(); + updateDirtyStates(region.translated(margins.left(), margins.top())); - if (mBackBuffer->image()->hasAlphaChannel()) { + // Although undocumented, QBackingStore::beginPaint expects the painted region + // to be cleared before use if the window has a surface format with an alpha. + // Fresh QWaylandShmBuffer are already cleared, so we don't need to clear those. + if (!bufferWasRecreated && mBackBuffer->image()->hasAlphaChannel()) { QPainter p(paintDevice()); p.setCompositionMode(QPainter::CompositionMode_Source); const QColor blank = Qt::transparent; @@ -160,14 +204,6 @@ void QWaylandShmBackingStore::endPaint() mPainting = false; if (mPendingFlush) flush(window(), mPendingRegion, QPoint()); - waylandWindow()->setCanResize(true); -} - -void QWaylandShmBackingStore::ensureSize() -{ - waylandWindow()->setBackingStore(this); - waylandWindow()->createDecoration(); - resize(mRequestedSize); } void QWaylandShmBackingStore::flush(QWindow *window, const QRegion ®ion, const QPoint &offset) @@ -206,8 +242,10 @@ void QWaylandShmBackingStore::resize(const QSize &size, const QRegion &) mRequestedSize = size; } -QWaylandShmBuffer *QWaylandShmBackingStore::getBuffer(const QSize &size) +QWaylandShmBuffer *QWaylandShmBackingStore::getBuffer(const QSize &size, bool &bufferWasRecreated) { + bufferWasRecreated = false; + const auto copy = mBuffers; // remove when ported to vector<unique_ptr> + remove_if for (QWaylandShmBuffer *b : copy) { if (!b->busy()) { @@ -226,40 +264,61 @@ QWaylandShmBuffer *QWaylandShmBackingStore::getBuffer(const QSize &size) if (mBuffers.size() < MAX_BUFFERS) { QImage::Format format = QPlatformScreen::platformScreenForWindow(window())->format(); QWaylandShmBuffer *b = new QWaylandShmBuffer(mDisplay, size, format, waylandWindow()->scale()); + bufferWasRecreated = true; mBuffers.push_front(b); return b; } return nullptr; } -void QWaylandShmBackingStore::resize(const QSize &size) +bool QWaylandShmBackingStore::recreateBackBufferIfNeeded() { + bool bufferWasRecreated = false; QMargins margins = windowDecorationMargins(); qreal scale = waylandWindow()->scale(); - QSize sizeWithMargins = (size + QSize(margins.left()+margins.right(),margins.top()+margins.bottom())) * scale; + const QSize sizeWithMargins = (mRequestedSize + 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. // You can exercise the different codepaths with weston, switching between the gl and the // pixman renderer. With the gl renderer release events are sent early so we can effectively // run single buffered, while with the pixman renderer we have to use two. - QWaylandShmBuffer *buffer = getBuffer(sizeWithMargins); + QWaylandShmBuffer *buffer = getBuffer(sizeWithMargins, bufferWasRecreated); while (!buffer) { qCDebug(lcWaylandBackingstore, "QWaylandShmBackingStore: stalling waiting for a buffer to be released from the compositor..."); mDisplay->blockingReadEvents(); - buffer = getBuffer(sizeWithMargins); + buffer = getBuffer(sizeWithMargins, bufferWasRecreated); } qsizetype oldSizeInBytes = mBackBuffer ? mBackBuffer->image()->sizeInBytes() : 0; 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; @@ -272,6 +331,10 @@ void QWaylandShmBackingStore::resize(const QSize &size) if (windowDecoration() && window()->isVisible() && oldSizeInBytes != newSizeInBytes) windowDecoration()->update(); + + buffer->dirtyRegion() = QRegion(); + + return bufferWasRecreated; } QImage *QWaylandShmBackingStore::entireSurface() const @@ -296,6 +359,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); @@ -303,16 +367,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); @@ -320,6 +387,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 |