summaryrefslogtreecommitdiffstats
path: root/src/client/qwaylandshmbackingstore.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/qwaylandshmbackingstore.cpp')
-rw-r--r--src/client/qwaylandshmbackingstore.cpp114
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 &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)
{
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 &region, 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