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
46QT_BEGIN_NAMESPACE
47
48extern void qt_scrollRectInImage(QImage &, const QRect &, const QPoint &);
49
50namespace QtWaylandClient {
51
52QWaylandShmBuffer::QWaylandShmBuffer(QWaylandDisplay *display,
53 const QSize &size, QImage::Format format, qreal scale, wl_event_queue *customEventQueue)
54 : mDirtyRegion(QRect(QPoint(0, 0), size / scale))
55{
56 int stride = size.width() * 4;
57 int alloc = stride * size.height();
58 int fd = -1;
59
60#ifdef SYS_memfd_create
61 fd = syscall(SYS_memfd_create, "wayland-shm", MFD_CLOEXEC | MFD_ALLOW_SEALING);
62 if (fd >= 0)
63 fcntl(fd: fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL);
64#endif
65
66 std::unique_ptr<QFile> filePointer;
67 bool opened;
68
69 if (fd == -1) {
70 auto tmpFile =
71 std::make_unique<QTemporaryFile>(args: QStandardPaths::writableLocation(type: QStandardPaths::RuntimeLocation) +
72 QLatin1String("/wayland-shm-XXXXXX"));
73 opened = tmpFile->open();
74 filePointer = std::move(tmpFile);
75 } else {
76 auto file = std::make_unique<QFile>();
77 opened = file->open(fd, ioFlags: QIODevice::ReadWrite | QIODevice::Unbuffered, handleFlags: QFile::AutoCloseHandle);
78 filePointer = std::move(file);
79 }
80 // NOTE beginPaint assumes a new buffer be all zeroes, which QFile::resize does.
81 if (!opened || !filePointer->resize(sz: alloc)) {
82 qWarning(msg: "QWaylandShmBuffer: failed: %s", qUtf8Printable(filePointer->errorString()));
83 return;
84 }
85 fd = filePointer->handle();
86
87 // map ourselves: QFile::map() will unmap when the object is destroyed,
88 // but we want this mapping to persist (unmapping in destructor)
89 uchar *data = (uchar *)
90 mmap(addr: nullptr, len: alloc, PROT_READ | PROT_WRITE, MAP_SHARED, fd: fd, offset: 0);
91 if (data == (uchar *) MAP_FAILED) {
92 qErrnoWarning(msg: "QWaylandShmBuffer: mmap failed");
93 return;
94 }
95
96 QWaylandShm* shm = display->shm();
97 wl_shm_format wl_format = shm->formatFrom(format);
98 mImage = QImage(data, size.width(), size.height(), stride, format);
99 mImage.setDevicePixelRatio(scale);
100
101 mShmPool = wl_shm_create_pool(shm->object(), fd, alloc);
102 init(wl_shm_pool_create_buffer(mShmPool,0, size.width(), size.height(),
103 stride, wl_format));
104 if (customEventQueue)
105 wl_proxy_set_queue(reinterpret_cast<struct wl_proxy *>(buffer()), customEventQueue);
106}
107
108QWaylandShmBuffer::~QWaylandShmBuffer(void)
109{
110 delete mMarginsImage;
111 if (mImage.constBits())
112 munmap(addr: (void *) mImage.constBits(), len: mImage.sizeInBytes());
113 if (mShmPool)
114 wl_shm_pool_destroy(mShmPool);
115}
116
117QImage *QWaylandShmBuffer::imageInsideMargins(const QMargins &marginsIn)
118{
119 QMargins margins = marginsIn * mImage.devicePixelRatio();
120
121 if (!margins.isNull() && margins != mMargins) {
122 if (mMarginsImage) {
123 delete mMarginsImage;
124 }
125 uchar *bits = const_cast<uchar *>(mImage.constBits());
126 uchar *b_s_data = bits + margins.top() * mImage.bytesPerLine() + margins.left() * 4;
127 int b_s_width = mImage.size().width() - margins.left() - margins.right();
128 int b_s_height = mImage.size().height() - margins.top() - margins.bottom();
129 mMarginsImage = new QImage(b_s_data, b_s_width,b_s_height,mImage.bytesPerLine(),mImage.format());
130 mMarginsImage->setDevicePixelRatio(mImage.devicePixelRatio());
131 }
132 if (margins.isNull()) {
133 delete mMarginsImage;
134 mMarginsImage = nullptr;
135 }
136
137 mMargins = margins;
138 if (!mMarginsImage)
139 return &mImage;
140
141 return mMarginsImage;
142
143}
144
145QWaylandShmBackingStore::QWaylandShmBackingStore(QWindow *window, QWaylandDisplay *display)
146 : QPlatformBackingStore(window)
147 , mDisplay(display)
148{
149 mEventQueue = wl_display_create_queue(mDisplay->wl_display());
150 QObject::connect(sender: mDisplay, signal: &QWaylandDisplay::connected, context: window, slot: [this]() {
151 auto oldEventQueue = mEventQueue;
152 mEventQueue = wl_display_create_queue(mDisplay->wl_display());
153 auto copy = mBuffers;
154 // clear available buffers so we create new ones
155 // actual deletion is deferred till after resize call so we can copy
156 // contents from the back buffer
157 mBuffers.clear();
158 mFrontBuffer = nullptr;
159 // recreateBackBufferIfNeeded always resets mBackBuffer
160 if (mRequestedSize.isValid() && waylandWindow())
161 recreateBackBufferIfNeeded();
162 qDeleteAll(c: copy);
163 wl_event_queue_destroy(oldEventQueue);
164 });
165}
166
167QWaylandShmBackingStore::~QWaylandShmBackingStore()
168{
169 if (QWaylandWindow *w = waylandWindow())
170 w->setBackingStore(nullptr);
171
172// if (mFrontBuffer == waylandWindow()->attached())
173// waylandWindow()->attach(0);
174
175 qDeleteAll(c: mBuffers);
176 wl_event_queue_destroy(mEventQueue);
177}
178
179QPaintDevice *QWaylandShmBackingStore::paintDevice()
180{
181 return contentSurface();
182}
183
184void QWaylandShmBackingStore::updateDirtyStates(const QRegion &region)
185{
186 // Update dirty state of buffers based on what was painted. The back buffer will
187 // not be dirty since we already painted on it, while other buffers will become dirty.
188 for (QWaylandShmBuffer *b : std::as_const(t&: mBuffers)) {
189 if (b != mBackBuffer)
190 b->dirtyRegion() += region;
191 }
192}
193
194void QWaylandShmBackingStore::beginPaint(const QRegion &region)
195{
196 mPainting = true;
197 waylandWindow()->setBackingStore(this);
198
199 const QMargins margins = windowDecorationMargins();
200 const QRegion regionTranslated = region.translated(dx: margins.left(), dy: margins.top());
201 const bool bufferWasRecreated = recreateBackBufferIfNeeded(nonDirtyRegion: regionTranslated);
202 updateDirtyStates(region: regionTranslated);
203
204 // Although undocumented, QBackingStore::beginPaint expects the painted region
205 // to be cleared before use if the window has a surface format with an alpha.
206 // Fresh QWaylandShmBuffer are already cleared, so we don't need to clear those.
207 if (!bufferWasRecreated && window()->format().hasAlpha()) {
208 QPainter p(paintDevice());
209 p.setCompositionMode(QPainter::CompositionMode_Source);
210 const QColor blank = Qt::transparent;
211 for (const QRect &rect : region)
212 p.fillRect(rect, color: blank);
213 }
214}
215
216void QWaylandShmBackingStore::endPaint()
217{
218 mPainting = false;
219 if (mPendingFlush)
220 flush(window: window(), region: mPendingRegion, offset: QPoint());
221}
222
223// Inspired by QCALayerBackingStore.
224bool QWaylandShmBackingStore::scroll(const QRegion &region, int dx, int dy)
225{
226 if (!mBackBuffer)
227 return false;
228
229 const qreal devicePixelRatio = waylandWindow()->scale();
230
231 // On Wayland, the window can have a device pixel ratio different from
232 // the window/screen, therefore we cannot rely on QHighDpi here, cf. QBackingStore::scroll.
233 // With fractional scaling we cannot easily scroll the existing pixels.
234 if (!qFuzzyIsNull(d: devicePixelRatio - static_cast<int>(devicePixelRatio)))
235 return false;
236
237 recreateBackBufferIfNeeded();
238
239 QImage *backBufferImage = mBackBuffer->image();
240
241 const QPoint scrollDelta(dx, dy);
242 const QMargins margins = windowDecorationMargins();
243 const QRegion adjustedRegion = region.translated(dx: margins.left(), dy: margins.top());
244
245 const QRect boundingRect = adjustedRegion.boundingRect();
246 const QPoint devicePixelDelta = scrollDelta * devicePixelRatio;
247
248 qt_scrollRectInImage(*backBufferImage,
249 QRect(boundingRect.topLeft() * devicePixelRatio,
250 boundingRect.size() * devicePixelRatio),
251 devicePixelDelta);
252
253 // We do not mark the source region as dirty, even though it technically has "moved".
254 // This matches the behavior of other backingstore implementations using qt_scrollRectInImage.
255 updateDirtyStates(region: adjustedRegion.translated(p: scrollDelta));
256
257 return true;
258}
259
260void QWaylandShmBackingStore::flush(QWindow *window, const QRegion &region, const QPoint &offset)
261{
262 Q_UNUSED(offset)
263 // Invoked when the window is of type RasterSurface or when the window is
264 // RasterGLSurface and there are no child widgets requiring OpenGL composition.
265
266 // For the case of RasterGLSurface + having to compose, the composeAndFlush() is
267 // called instead. The default implementation from QPlatformBackingStore is sufficient
268 // however so no need to reimplement that.
269 if (window != this->window()) {
270 auto waylandWindow = static_cast<QWaylandWindow *>(window->handle());
271 auto newBuffer = new QWaylandShmBuffer(mDisplay, window->size(), mBackBuffer->image()->format(), mBackBuffer->scale(), mEventQueue);
272 newBuffer->setDeleteOnRelease(true);
273 QRect sourceRect(window->position(), window->size());
274 QPainter painter(newBuffer->image());
275 painter.drawImage(p: QPoint(0, 0), image: *mBackBuffer->image(), sr: sourceRect);
276 waylandWindow->safeCommit(buffer: newBuffer, damage: region);
277 return;
278 }
279
280 if (mPainting) {
281 mPendingRegion |= region;
282 mPendingFlush = true;
283 return;
284 }
285
286 mPendingFlush = false;
287 mPendingRegion = QRegion();
288
289 if (windowDecoration() && windowDecoration()->isDirty())
290 updateDecorations();
291
292 mFrontBuffer = mBackBuffer;
293
294 QMargins margins = windowDecorationMargins();
295 waylandWindow()->safeCommit(buffer: mFrontBuffer, damage: region.translated(dx: margins.left(), dy: margins.top()));
296}
297
298void QWaylandShmBackingStore::resize(const QSize &size, const QRegion &)
299{
300 mRequestedSize = size;
301}
302
303QWaylandShmBuffer *QWaylandShmBackingStore::getBuffer(const QSize &size, bool &bufferWasRecreated)
304{
305 static const int MAX_BUFFERS = 5;
306 static const int MAX_AGE = 10 * MAX_BUFFERS;
307 bufferWasRecreated = false;
308
309 // Prune buffers that have not been used in a while or with different size.
310 for (auto i = mBuffers.size() - 1; i >= 0; --i) {
311 QWaylandShmBuffer *buffer = mBuffers[i];
312 if (buffer->age() > MAX_AGE || buffer->size() != size) {
313 mBuffers.removeAt(i);
314 if (mBackBuffer == buffer)
315 mBackBuffer = nullptr;
316 delete buffer;
317 }
318 }
319
320 QWaylandShmBuffer *buffer = nullptr;
321 for (QWaylandShmBuffer *candidate : std::as_const(t&: mBuffers)) {
322 if (candidate->busy())
323 continue;
324
325 if (!buffer || candidate->age() < buffer->age())
326 buffer = candidate;
327 }
328
329 if (buffer)
330 return buffer;
331
332 if (mBuffers.size() < MAX_BUFFERS) {
333 QImage::Format format = QImage::Format_ARGB32_Premultiplied;
334 if (!waylandWindow()->format().hasAlpha())
335 format = QImage::Format_RGB32;
336 QWaylandShmBuffer *b = new QWaylandShmBuffer(mDisplay, size, format, waylandWindow()->scale(), mEventQueue);
337 bufferWasRecreated = true;
338 mBuffers.push_front(t: b);
339 return b;
340 }
341 return nullptr;
342}
343
344bool QWaylandShmBackingStore::recreateBackBufferIfNeeded(const QRegion &nonDirtyRegion)
345{
346 wl_display_dispatch_queue_pending(mDisplay->wl_display(), mEventQueue);
347
348 bool bufferWasRecreated = false;
349 QMargins margins = windowDecorationMargins();
350 qreal scale = waylandWindow()->scale();
351 const QSize sizeWithMargins = (mRequestedSize + QSize(margins.left() + margins.right(), margins.top() + margins.bottom())) * scale;
352
353 // We look for a free buffer to draw into. If the buffer is not the last buffer we used,
354 // that is mBackBuffer, and the size is the same we copy the damaged content into the new
355 // buffer so that QPainter is happy to find the stuff it had drawn before. If the new
356 // buffer has a different size it needs to be redrawn completely anyway, and if the buffer
357 // is the same the stuff is there already.
358 // You can exercise the different codepaths with weston, switching between the gl and the
359 // pixman renderer. With the gl renderer release events are sent early so we can effectively
360 // run single buffered, while with the pixman renderer we have to use two.
361 QWaylandShmBuffer *buffer = getBuffer(size: sizeWithMargins, bufferWasRecreated);
362 while (!buffer) {
363 struct ::wl_display *display = mDisplay->wl_display();
364 if (wl_display_dispatch_queue(display, mEventQueue) < 0) {
365 int ecode = wl_display_get_error(display);
366 if ((ecode == EPIPE || ecode == ECONNRESET))
367 qWarning(msg: "The Wayland connection broke during blocking read event. Did the Wayland compositor die?");
368 else
369 qWarning(msg: "The Wayland connection experienced a fatal error during blocking read event: %s", strerror(errnum: ecode));
370 _exit(status: -1);
371 }
372 buffer = getBuffer(size: sizeWithMargins, bufferWasRecreated);
373 }
374
375 qsizetype oldSizeInBytes = mBackBuffer ? mBackBuffer->image()->sizeInBytes() : 0;
376 qsizetype newSizeInBytes = buffer->image()->sizeInBytes();
377
378 // mBackBuffer may have been deleted here but if so it means its size was different so we wouldn't copy it anyway
379 if (mBackBuffer != buffer && oldSizeInBytes == newSizeInBytes) {
380 const QRegion clipRegion = buffer->dirtyRegion() - nonDirtyRegion;
381 const auto clipRects = clipRegion.rects();
382 if (!clipRects.empty()) {
383 Q_ASSERT(mBackBuffer);
384 const QImage *sourceImage = mBackBuffer->image();
385 QImage *targetImage = buffer->image();
386
387 QPainter painter(targetImage);
388 painter.setCompositionMode(QPainter::CompositionMode_Source);
389 const qreal targetDevicePixelRatio = painter.device()->devicePixelRatio();
390 for (const QRect &clipRect : clipRects) { // Iterate clip rects, because complicated clip region causes higher CPU usage
391 if (clipRects.size() > 1)
392 painter.save();
393 painter.setClipRect(clipRect);
394 painter.scale(sx: qreal(1) / targetDevicePixelRatio, sy: qreal(1) / targetDevicePixelRatio);
395 painter.drawImage(targetRect: QRectF(QPointF(), targetImage->size()), image: *sourceImage, sourceRect: sourceImage->rect());
396 if (clipRects.size() > 1)
397 painter.restore();
398 }
399 }
400 }
401
402 mBackBuffer = buffer;
403
404 for (QWaylandShmBuffer *buffer : std::as_const(t&: mBuffers)) {
405 if (mBackBuffer == buffer) {
406 buffer->setAge(0);
407 } else {
408 buffer->setAge(buffer->age() + 1);
409 }
410 }
411
412 if (windowDecoration() && window()->isVisible() && oldSizeInBytes != newSizeInBytes)
413 windowDecoration()->update();
414
415 buffer->dirtyRegion() = QRegion();
416
417 return bufferWasRecreated;
418}
419
420QImage *QWaylandShmBackingStore::entireSurface() const
421{
422 return mBackBuffer->image();
423}
424
425QImage *QWaylandShmBackingStore::contentSurface() const
426{
427 return windowDecoration() ? mBackBuffer->imageInsideMargins(marginsIn: windowDecorationMargins()) : mBackBuffer->image();
428}
429
430void QWaylandShmBackingStore::updateDecorations()
431{
432 QPainter decorationPainter(entireSurface());
433 decorationPainter.setCompositionMode(QPainter::CompositionMode_Source);
434 QImage sourceImage = windowDecoration()->contentImage();
435
436 qreal dp = sourceImage.devicePixelRatio();
437 int dpWidth = int(sourceImage.width() / dp);
438 int dpHeight = int(sourceImage.height() / dp);
439 QTransform sourceMatrix;
440 sourceMatrix.scale(sx: dp, sy: dp);
441 QRect target; // needs to be in device independent pixels
442 QRegion dirtyRegion;
443
444 //Top
445 target.setX(0);
446 target.setY(0);
447 target.setWidth(dpWidth);
448 target.setHeight(windowDecorationMargins().top());
449 decorationPainter.drawImage(targetRect: target, image: sourceImage, sourceRect: sourceMatrix.mapRect(target));
450 dirtyRegion += target;
451
452 //Left
453 target.setWidth(windowDecorationMargins().left());
454 target.setHeight(dpHeight);
455 decorationPainter.drawImage(targetRect: target, image: sourceImage, sourceRect: sourceMatrix.mapRect(target));
456 dirtyRegion += target;
457
458 //Right
459 target.setX(dpWidth - windowDecorationMargins().right());
460 target.setWidth(windowDecorationMargins().right());
461 decorationPainter.drawImage(targetRect: target, image: sourceImage, sourceRect: sourceMatrix.mapRect(target));
462 dirtyRegion += target;
463
464 //Bottom
465 target.setX(0);
466 target.setY(dpHeight - windowDecorationMargins().bottom());
467 target.setWidth(dpWidth);
468 target.setHeight(windowDecorationMargins().bottom());
469 decorationPainter.drawImage(targetRect: target, image: sourceImage, sourceRect: sourceMatrix.mapRect(target));
470 dirtyRegion += target;
471
472 updateDirtyStates(region: dirtyRegion);
473}
474
475QWaylandAbstractDecoration *QWaylandShmBackingStore::windowDecoration() const
476{
477 return waylandWindow()->decoration();
478}
479
480QMargins QWaylandShmBackingStore::windowDecorationMargins() const
481{
482 if (windowDecoration())
483 return windowDecoration()->margins();
484 return QMargins();
485}
486
487QWaylandWindow *QWaylandShmBackingStore::waylandWindow() const
488{
489 return static_cast<QWaylandWindow *>(window()->handle());
490}
491
492#if QT_CONFIG(opengl)
493QImage QWaylandShmBackingStore::toImage() const
494{
495 // Invoked from QPlatformBackingStore::composeAndFlush() that is called
496 // instead of flush() for widgets that have renderToTexture children
497 // (QOpenGLWidget, QQuickWidget).
498
499 return *contentSurface();
500}
501#endif // opengl
502
503}
504
505QT_END_NAMESPACE
506

source code of qtbase/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp