| 1 | // Copyright (C) 2016 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
| 3 | #include "qwaylandshmbackingstore_p.h" |
| 4 | #include "qwaylandwindow_p.h" |
| 5 | #include "qwaylandsubsurface_p.h" |
| 6 | #include "qwaylanddisplay_p.h" |
| 7 | #include "qwaylandscreen_p.h" |
| 8 | #include "qwaylandabstractdecoration_p.h" |
| 9 | |
| 10 | #include <QtCore/qdebug.h> |
| 11 | #include <QtCore/qstandardpaths.h> |
| 12 | #include <QtCore/qtemporaryfile.h> |
| 13 | #include <QtGui/QPainter> |
| 14 | #include <QtGui/QTransform> |
| 15 | #include <QMutexLocker> |
| 16 | |
| 17 | #include <QtWaylandClient/private/wayland-wayland-client-protocol.h> |
| 18 | |
| 19 | #include <memory> |
| 20 | |
| 21 | #include <fcntl.h> |
| 22 | #include <unistd.h> |
| 23 | #include <sys/mman.h> |
| 24 | |
| 25 | #ifdef Q_OS_LINUX |
| 26 | # include <sys/syscall.h> |
| 27 | // from linux/memfd.h: |
| 28 | # ifndef MFD_CLOEXEC |
| 29 | # define MFD_CLOEXEC 0x0001U |
| 30 | # endif |
| 31 | # ifndef MFD_ALLOW_SEALING |
| 32 | # define MFD_ALLOW_SEALING 0x0002U |
| 33 | # endif |
| 34 | // from bits/fcntl-linux.h |
| 35 | # ifndef F_ADD_SEALS |
| 36 | # define F_ADD_SEALS 1033 |
| 37 | # endif |
| 38 | # ifndef F_SEAL_SEAL |
| 39 | # define F_SEAL_SEAL 0x0001 |
| 40 | # endif |
| 41 | # ifndef F_SEAL_SHRINK |
| 42 | # define F_SEAL_SHRINK 0x0002 |
| 43 | # endif |
| 44 | #endif |
| 45 | |
| 46 | QT_BEGIN_NAMESPACE |
| 47 | |
| 48 | namespace QtWaylandClient { |
| 49 | |
| 50 | QWaylandShmBuffer::QWaylandShmBuffer(QWaylandDisplay *display, |
| 51 | const QSize &size, QImage::Format format, qreal scale) |
| 52 | : mDirtyRegion(QRect(QPoint(0, 0), size / scale)) |
| 53 | { |
| 54 | int stride = size.width() * 4; |
| 55 | int alloc = stride * size.height(); |
| 56 | int fd = -1; |
| 57 | |
| 58 | #ifdef SYS_memfd_create |
| 59 | fd = syscall(SYS_memfd_create, "wayland-shm" , MFD_CLOEXEC | MFD_ALLOW_SEALING); |
| 60 | if (fd >= 0) |
| 61 | fcntl(fd: fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL); |
| 62 | #endif |
| 63 | |
| 64 | std::unique_ptr<QFile> filePointer; |
| 65 | bool opened; |
| 66 | |
| 67 | if (fd == -1) { |
| 68 | auto tmpFile = |
| 69 | std::make_unique<QTemporaryFile>(args: QStandardPaths::writableLocation(type: QStandardPaths::RuntimeLocation) + |
| 70 | QLatin1String("/wayland-shm-XXXXXX" )); |
| 71 | opened = tmpFile->open(); |
| 72 | filePointer = std::move(tmpFile); |
| 73 | } else { |
| 74 | auto file = std::make_unique<QFile>(); |
| 75 | opened = file->open(fd, ioFlags: QIODevice::ReadWrite | QIODevice::Unbuffered, handleFlags: QFile::AutoCloseHandle); |
| 76 | filePointer = std::move(file); |
| 77 | } |
| 78 | // NOTE beginPaint assumes a new buffer be all zeroes, which QFile::resize does. |
| 79 | if (!opened || !filePointer->resize(sz: alloc)) { |
| 80 | qWarning(msg: "QWaylandShmBuffer: failed: %s" , qUtf8Printable(filePointer->errorString())); |
| 81 | return; |
| 82 | } |
| 83 | fd = filePointer->handle(); |
| 84 | |
| 85 | // map ourselves: QFile::map() will unmap when the object is destroyed, |
| 86 | // but we want this mapping to persist (unmapping in destructor) |
| 87 | uchar *data = (uchar *) |
| 88 | mmap(addr: nullptr, len: alloc, PROT_READ | PROT_WRITE, MAP_SHARED, fd: fd, offset: 0); |
| 89 | if (data == (uchar *) MAP_FAILED) { |
| 90 | qErrnoWarning(msg: "QWaylandShmBuffer: mmap failed" ); |
| 91 | return; |
| 92 | } |
| 93 | |
| 94 | QWaylandShm* shm = display->shm(); |
| 95 | wl_shm_format wl_format = shm->formatFrom(format); |
| 96 | mImage = QImage(data, size.width(), size.height(), stride, format); |
| 97 | mImage.setDevicePixelRatio(scale); |
| 98 | |
| 99 | mShmPool = wl_shm_create_pool(shm->object(), fd, alloc); |
| 100 | init(wl_shm_pool_create_buffer(mShmPool,0, size.width(), size.height(), |
| 101 | stride, wl_format)); |
| 102 | } |
| 103 | |
| 104 | QWaylandShmBuffer::~QWaylandShmBuffer(void) |
| 105 | { |
| 106 | delete mMarginsImage; |
| 107 | if (mImage.constBits()) |
| 108 | munmap(addr: (void *) mImage.constBits(), len: mImage.sizeInBytes()); |
| 109 | if (mShmPool) |
| 110 | wl_shm_pool_destroy(mShmPool); |
| 111 | } |
| 112 | |
| 113 | QImage *QWaylandShmBuffer::imageInsideMargins(const QMargins &marginsIn) |
| 114 | { |
| 115 | QMargins margins = marginsIn * mImage.devicePixelRatio(); |
| 116 | |
| 117 | if (!margins.isNull() && margins != mMargins) { |
| 118 | if (mMarginsImage) { |
| 119 | delete mMarginsImage; |
| 120 | } |
| 121 | uchar *bits = const_cast<uchar *>(mImage.constBits()); |
| 122 | uchar *b_s_data = bits + margins.top() * mImage.bytesPerLine() + margins.left() * 4; |
| 123 | int b_s_width = mImage.size().width() - margins.left() - margins.right(); |
| 124 | int b_s_height = mImage.size().height() - margins.top() - margins.bottom(); |
| 125 | mMarginsImage = new QImage(b_s_data, b_s_width,b_s_height,mImage.bytesPerLine(),mImage.format()); |
| 126 | mMarginsImage->setDevicePixelRatio(mImage.devicePixelRatio()); |
| 127 | } |
| 128 | if (margins.isNull()) { |
| 129 | delete mMarginsImage; |
| 130 | mMarginsImage = nullptr; |
| 131 | } |
| 132 | |
| 133 | mMargins = margins; |
| 134 | if (!mMarginsImage) |
| 135 | return &mImage; |
| 136 | |
| 137 | return mMarginsImage; |
| 138 | |
| 139 | } |
| 140 | |
| 141 | QWaylandShmBackingStore::QWaylandShmBackingStore(QWindow *window, QWaylandDisplay *display) |
| 142 | : QPlatformBackingStore(window) |
| 143 | , mDisplay(display) |
| 144 | { |
| 145 | QObject::connect(sender: mDisplay, signal: &QWaylandDisplay::connected, context: window, slot: [this]() { |
| 146 | auto copy = mBuffers; |
| 147 | // clear available buffers so we create new ones |
| 148 | // actual deletion is deferred till after resize call so we can copy |
| 149 | // contents from the back buffer |
| 150 | mBuffers.clear(); |
| 151 | mFrontBuffer = nullptr; |
| 152 | // recreateBackBufferIfNeeded always resets mBackBuffer |
| 153 | if (mRequestedSize.isValid() && waylandWindow()) |
| 154 | recreateBackBufferIfNeeded(); |
| 155 | qDeleteAll(c: copy); |
| 156 | }); |
| 157 | } |
| 158 | |
| 159 | QWaylandShmBackingStore::~QWaylandShmBackingStore() |
| 160 | { |
| 161 | if (QWaylandWindow *w = waylandWindow()) |
| 162 | w->setBackingStore(nullptr); |
| 163 | |
| 164 | // if (mFrontBuffer == waylandWindow()->attached()) |
| 165 | // waylandWindow()->attach(0); |
| 166 | |
| 167 | qDeleteAll(c: mBuffers); |
| 168 | } |
| 169 | |
| 170 | QPaintDevice *QWaylandShmBackingStore::paintDevice() |
| 171 | { |
| 172 | return contentSurface(); |
| 173 | } |
| 174 | |
| 175 | void QWaylandShmBackingStore::updateDirtyStates(const QRegion ®ion) |
| 176 | { |
| 177 | // Update dirty state of buffers based on what was painted. The back buffer will |
| 178 | // not be dirty since we already painted on it, while other buffers will become dirty. |
| 179 | for (QWaylandShmBuffer *b : std::as_const(t&: mBuffers)) { |
| 180 | if (b != mBackBuffer) |
| 181 | b->dirtyRegion() += region; |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | void QWaylandShmBackingStore::beginPaint(const QRegion ®ion) |
| 186 | { |
| 187 | mPainting = true; |
| 188 | waylandWindow()->setBackingStore(this); |
| 189 | const bool bufferWasRecreated = recreateBackBufferIfNeeded(); |
| 190 | |
| 191 | const QMargins margins = windowDecorationMargins(); |
| 192 | updateDirtyStates(region: region.translated(dx: margins.left(), dy: margins.top())); |
| 193 | |
| 194 | // Although undocumented, QBackingStore::beginPaint expects the painted region |
| 195 | // to be cleared before use if the window has a surface format with an alpha. |
| 196 | // Fresh QWaylandShmBuffer are already cleared, so we don't need to clear those. |
| 197 | if (!bufferWasRecreated && mBackBuffer->image()->hasAlphaChannel()) { |
| 198 | QPainter p(paintDevice()); |
| 199 | p.setCompositionMode(QPainter::CompositionMode_Source); |
| 200 | const QColor blank = Qt::transparent; |
| 201 | for (const QRect &rect : region) |
| 202 | p.fillRect(rect, color: blank); |
| 203 | } |
| 204 | } |
| 205 | |
| 206 | void QWaylandShmBackingStore::endPaint() |
| 207 | { |
| 208 | mPainting = false; |
| 209 | if (mPendingFlush) |
| 210 | flush(window: window(), region: mPendingRegion, offset: QPoint()); |
| 211 | } |
| 212 | |
| 213 | void QWaylandShmBackingStore::flush(QWindow *window, const QRegion ®ion, const QPoint &offset) |
| 214 | { |
| 215 | Q_UNUSED(offset) |
| 216 | // Invoked when the window is of type RasterSurface or when the window is |
| 217 | // RasterGLSurface and there are no child widgets requiring OpenGL composition. |
| 218 | |
| 219 | // For the case of RasterGLSurface + having to compose, the composeAndFlush() is |
| 220 | // called instead. The default implementation from QPlatformBackingStore is sufficient |
| 221 | // however so no need to reimplement that. |
| 222 | if (window != this->window()) { |
| 223 | auto waylandWindow = static_cast<QWaylandWindow *>(window->handle()); |
| 224 | auto newBuffer = new QWaylandShmBuffer(mDisplay, window->size(), mBackBuffer->image()->format(), mBackBuffer->scale()); |
| 225 | newBuffer->setDeleteOnRelease(true); |
| 226 | QRect sourceRect(window->position(), window->size()); |
| 227 | QPainter painter(newBuffer->image()); |
| 228 | painter.drawImage(p: QPoint(0, 0), image: *mBackBuffer->image(), sr: sourceRect); |
| 229 | waylandWindow->safeCommit(buffer: newBuffer, damage: region); |
| 230 | return; |
| 231 | } |
| 232 | |
| 233 | if (mPainting) { |
| 234 | mPendingRegion |= region; |
| 235 | mPendingFlush = true; |
| 236 | return; |
| 237 | } |
| 238 | |
| 239 | mPendingFlush = false; |
| 240 | mPendingRegion = QRegion(); |
| 241 | |
| 242 | if (windowDecoration() && windowDecoration()->isDirty()) |
| 243 | updateDecorations(); |
| 244 | |
| 245 | mFrontBuffer = mBackBuffer; |
| 246 | |
| 247 | QMargins margins = windowDecorationMargins(); |
| 248 | waylandWindow()->safeCommit(buffer: mFrontBuffer, damage: region.translated(dx: margins.left(), dy: margins.top())); |
| 249 | } |
| 250 | |
| 251 | void QWaylandShmBackingStore::resize(const QSize &size, const QRegion &) |
| 252 | { |
| 253 | mRequestedSize = size; |
| 254 | } |
| 255 | |
| 256 | QWaylandShmBuffer *QWaylandShmBackingStore::getBuffer(const QSize &size, bool &bufferWasRecreated) |
| 257 | { |
| 258 | bufferWasRecreated = false; |
| 259 | |
| 260 | const auto copy = mBuffers; // remove when ported to vector<unique_ptr> + remove_if |
| 261 | for (QWaylandShmBuffer *b : copy) { |
| 262 | if (!b->busy()) { |
| 263 | if (b->size() == size) { |
| 264 | return b; |
| 265 | } else { |
| 266 | mBuffers.remove(value: b); |
| 267 | if (mBackBuffer == b) |
| 268 | mBackBuffer = nullptr; |
| 269 | delete b; |
| 270 | } |
| 271 | } |
| 272 | } |
| 273 | |
| 274 | static const size_t MAX_BUFFERS = 5; |
| 275 | if (mBuffers.size() < MAX_BUFFERS) { |
| 276 | QImage::Format format = QPlatformScreen::platformScreenForWindow(window: window())->format(); |
| 277 | QWaylandShmBuffer *b = new QWaylandShmBuffer(mDisplay, size, format, waylandWindow()->scale()); |
| 278 | bufferWasRecreated = true; |
| 279 | mBuffers.push_front(x: b); |
| 280 | return b; |
| 281 | } |
| 282 | return nullptr; |
| 283 | } |
| 284 | |
| 285 | bool QWaylandShmBackingStore::recreateBackBufferIfNeeded() |
| 286 | { |
| 287 | bool bufferWasRecreated = false; |
| 288 | QMargins margins = windowDecorationMargins(); |
| 289 | qreal scale = waylandWindow()->scale(); |
| 290 | const QSize sizeWithMargins = (mRequestedSize + QSize(margins.left() + margins.right(), margins.top() + margins.bottom())) * scale; |
| 291 | |
| 292 | // We look for a free buffer to draw into. If the buffer is not the last buffer we used, |
| 293 | // that is mBackBuffer, and the size is the same we copy the damaged content into the new |
| 294 | // buffer so that QPainter is happy to find the stuff it had drawn before. If the new |
| 295 | // buffer has a different size it needs to be redrawn completely anyway, and if the buffer |
| 296 | // is the same the stuff is there already. |
| 297 | // You can exercise the different codepaths with weston, switching between the gl and the |
| 298 | // pixman renderer. With the gl renderer release events are sent early so we can effectively |
| 299 | // run single buffered, while with the pixman renderer we have to use two. |
| 300 | QWaylandShmBuffer *buffer = getBuffer(size: sizeWithMargins, bufferWasRecreated); |
| 301 | while (!buffer) { |
| 302 | qCDebug(lcWaylandBackingstore, "QWaylandShmBackingStore: stalling waiting for a buffer to be released from the compositor..." ); |
| 303 | |
| 304 | mDisplay->blockingReadEvents(); |
| 305 | buffer = getBuffer(size: sizeWithMargins, bufferWasRecreated); |
| 306 | } |
| 307 | |
| 308 | qsizetype oldSizeInBytes = mBackBuffer ? mBackBuffer->image()->sizeInBytes() : 0; |
| 309 | qsizetype newSizeInBytes = buffer->image()->sizeInBytes(); |
| 310 | |
| 311 | // mBackBuffer may have been deleted here but if so it means its size was different so we wouldn't copy it anyway |
| 312 | if (mBackBuffer != buffer && oldSizeInBytes == newSizeInBytes) { |
| 313 | Q_ASSERT(mBackBuffer); |
| 314 | const QImage *sourceImage = mBackBuffer->image(); |
| 315 | QImage *targetImage = buffer->image(); |
| 316 | |
| 317 | QPainter painter(targetImage); |
| 318 | painter.setCompositionMode(QPainter::CompositionMode_Source); |
| 319 | |
| 320 | const qreal sourceDevicePixelRatio = sourceImage->devicePixelRatio(); |
| 321 | for (const QRect &rect : buffer->dirtyRegion()) { |
| 322 | QRectF sourceRect(QPointF(rect.topLeft()) * sourceDevicePixelRatio, |
| 323 | QSizeF(rect.size()) * sourceDevicePixelRatio); |
| 324 | painter.drawImage(targetRect: rect, image: *sourceImage, sourceRect); |
| 325 | } |
| 326 | } |
| 327 | |
| 328 | mBackBuffer = buffer; |
| 329 | |
| 330 | // ensure the new buffer is at the beginning of the list so next time getBuffer() will pick |
| 331 | // it if possible |
| 332 | if (mBuffers.front() != buffer) { |
| 333 | mBuffers.remove(value: buffer); |
| 334 | mBuffers.push_front(x: buffer); |
| 335 | } |
| 336 | |
| 337 | if (windowDecoration() && window()->isVisible() && oldSizeInBytes != newSizeInBytes) |
| 338 | windowDecoration()->update(); |
| 339 | |
| 340 | buffer->dirtyRegion() = QRegion(); |
| 341 | |
| 342 | return bufferWasRecreated; |
| 343 | } |
| 344 | |
| 345 | QImage *QWaylandShmBackingStore::entireSurface() const |
| 346 | { |
| 347 | return mBackBuffer->image(); |
| 348 | } |
| 349 | |
| 350 | QImage *QWaylandShmBackingStore::contentSurface() const |
| 351 | { |
| 352 | return windowDecoration() ? mBackBuffer->imageInsideMargins(marginsIn: windowDecorationMargins()) : mBackBuffer->image(); |
| 353 | } |
| 354 | |
| 355 | void QWaylandShmBackingStore::updateDecorations() |
| 356 | { |
| 357 | QPainter decorationPainter(entireSurface()); |
| 358 | decorationPainter.setCompositionMode(QPainter::CompositionMode_Source); |
| 359 | QImage sourceImage = windowDecoration()->contentImage(); |
| 360 | |
| 361 | qreal dp = sourceImage.devicePixelRatio(); |
| 362 | int dpWidth = int(sourceImage.width() / dp); |
| 363 | int dpHeight = int(sourceImage.height() / dp); |
| 364 | QTransform sourceMatrix; |
| 365 | sourceMatrix.scale(sx: dp, sy: dp); |
| 366 | QRect target; // needs to be in device independent pixels |
| 367 | QRegion dirtyRegion; |
| 368 | |
| 369 | //Top |
| 370 | target.setX(0); |
| 371 | target.setY(0); |
| 372 | target.setWidth(dpWidth); |
| 373 | target.setHeight(windowDecorationMargins().top()); |
| 374 | decorationPainter.drawImage(targetRect: target, image: sourceImage, sourceRect: sourceMatrix.mapRect(target)); |
| 375 | dirtyRegion += target; |
| 376 | |
| 377 | //Left |
| 378 | target.setWidth(windowDecorationMargins().left()); |
| 379 | target.setHeight(dpHeight); |
| 380 | decorationPainter.drawImage(targetRect: target, image: sourceImage, sourceRect: sourceMatrix.mapRect(target)); |
| 381 | dirtyRegion += target; |
| 382 | |
| 383 | //Right |
| 384 | target.setX(dpWidth - windowDecorationMargins().right()); |
| 385 | target.setWidth(windowDecorationMargins().right()); |
| 386 | decorationPainter.drawImage(targetRect: target, image: sourceImage, sourceRect: sourceMatrix.mapRect(target)); |
| 387 | dirtyRegion += target; |
| 388 | |
| 389 | //Bottom |
| 390 | target.setX(0); |
| 391 | target.setY(dpHeight - windowDecorationMargins().bottom()); |
| 392 | target.setWidth(dpWidth); |
| 393 | target.setHeight(windowDecorationMargins().bottom()); |
| 394 | decorationPainter.drawImage(targetRect: target, image: sourceImage, sourceRect: sourceMatrix.mapRect(target)); |
| 395 | dirtyRegion += target; |
| 396 | |
| 397 | updateDirtyStates(region: dirtyRegion); |
| 398 | } |
| 399 | |
| 400 | QWaylandAbstractDecoration *QWaylandShmBackingStore::windowDecoration() const |
| 401 | { |
| 402 | return waylandWindow()->decoration(); |
| 403 | } |
| 404 | |
| 405 | QMargins QWaylandShmBackingStore::windowDecorationMargins() const |
| 406 | { |
| 407 | if (windowDecoration()) |
| 408 | return windowDecoration()->margins(); |
| 409 | return QMargins(); |
| 410 | } |
| 411 | |
| 412 | QWaylandWindow *QWaylandShmBackingStore::waylandWindow() const |
| 413 | { |
| 414 | return static_cast<QWaylandWindow *>(window()->handle()); |
| 415 | } |
| 416 | |
| 417 | #if QT_CONFIG(opengl) |
| 418 | QImage QWaylandShmBackingStore::toImage() const |
| 419 | { |
| 420 | // Invoked from QPlatformBackingStore::composeAndFlush() that is called |
| 421 | // instead of flush() for widgets that have renderToTexture children |
| 422 | // (QOpenGLWidget, QQuickWidget). |
| 423 | |
| 424 | return *contentSurface(); |
| 425 | } |
| 426 | #endif // opengl |
| 427 | |
| 428 | } |
| 429 | |
| 430 | QT_END_NAMESPACE |
| 431 | |