diff options
Diffstat (limited to 'src/client/qwaylandshmbackingstore.cpp')
-rw-r--r-- | src/client/qwaylandshmbackingstore.cpp | 167 |
1 files changed, 101 insertions, 66 deletions
diff --git a/src/client/qwaylandshmbackingstore.cpp b/src/client/qwaylandshmbackingstore.cpp index 9b5971a21..d77c548a0 100644 --- a/src/client/qwaylandshmbackingstore.cpp +++ b/src/client/qwaylandshmbackingstore.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qwaylandshmbackingstore_p.h" #include "qwaylandwindow_p.h" #include "qwaylandsubsurface_p.h" @@ -47,10 +11,12 @@ #include <QtCore/qstandardpaths.h> #include <QtCore/qtemporaryfile.h> #include <QtGui/QPainter> +#include <QtGui/QTransform> #include <QMutexLocker> #include <QtWaylandClient/private/wayland-wayland-client-protocol.h> +#include <fcntl.h> #include <unistd.h> #include <sys/mman.h> @@ -60,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 @@ -67,14 +46,17 @@ QT_BEGIN_NAMESPACE namespace QtWaylandClient { QWaylandShmBuffer::QWaylandShmBuffer(QWaylandDisplay *display, - const QSize &size, QImage::Format format, int scale) + 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; @@ -89,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; @@ -107,7 +90,7 @@ QWaylandShmBuffer::QWaylandShmBuffer(QWaylandDisplay *display, QWaylandShm* shm = display->shm(); wl_shm_format wl_format = shm->formatFrom(format); mImage = QImage(data, size.width(), size.height(), stride, format); - mImage.setDevicePixelRatio(qreal(scale)); + mImage.setDevicePixelRatio(scale); mShmPool = wl_shm_create_pool(shm->object(), fd, alloc); init(wl_shm_pool_create_buffer(mShmPool,0, size.width(), size.height(), @@ -125,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) { @@ -151,11 +134,22 @@ QImage *QWaylandShmBuffer::imageInsideMargins(const QMargins &marginsIn) } -QWaylandShmBackingStore::QWaylandShmBackingStore(QWindow *window) +QWaylandShmBackingStore::QWaylandShmBackingStore(QWindow *window, QWaylandDisplay *display) : QPlatformBackingStore(window) - , mDisplay(QWaylandScreen::waylandScreenFromWindow(window)->display()) + , 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() @@ -174,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; @@ -195,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) @@ -241,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()) { @@ -261,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(); - int scale = waylandWindow()->scale(); - QSize sizeWithMargins = (size + QSize(margins.left()+margins.right(),margins.top()+margins.bottom())) * scale; + qreal scale = waylandWindow()->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; @@ -307,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 @@ -328,9 +356,10 @@ void QWaylandShmBackingStore::updateDecorations() qreal dp = sourceImage.devicePixelRatio(); int dpWidth = int(sourceImage.width() / dp); int dpHeight = int(sourceImage.height() / dp); - QMatrix sourceMatrix; + QTransform sourceMatrix; sourceMatrix.scale(dp, dp); QRect target; // needs to be in device independent pixels + QRegion dirtyRegion; //Top target.setX(0); @@ -338,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); @@ -355,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 |