Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwaylandshmbackingstore.cpp
Go to the documentation of this file.
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// Qt-Security score:significant reason:default
10
11#include <QtCore/qdebug.h>
12#include <QtCore/qstandardpaths.h>
13#include <QtCore/qtemporaryfile.h>
14#include <QtGui/QPainter>
15#include <QtGui/QTransform>
16#include <QMutexLocker>
17
18#include <QtWaylandClient/private/wayland-wayland-client-protocol.h>
19
20#include <memory>
21
22#include <fcntl.h>
23#include <unistd.h>
24#include <sys/mman.h>
25
26#ifdef Q_OS_LINUX
27# include <sys/syscall.h>
28// from linux/memfd.h:
29# ifndef MFD_CLOEXEC
30# define MFD_CLOEXEC 0x0001U
31# endif
32# ifndef MFD_ALLOW_SEALING
33# define MFD_ALLOW_SEALING 0x0002U
34# endif
35// from bits/fcntl-linux.h
36# ifndef F_ADD_SEALS
37# define F_ADD_SEALS 1033
38# endif
39# ifndef F_SEAL_SEAL
40# define F_SEAL_SEAL 0x0001
41# endif
42# ifndef F_SEAL_SHRINK
43# define F_SEAL_SHRINK 0x0002
44# endif
45#endif
46
48
49extern void qt_scrollRectInImage(QImage &, const QRect &, const QPoint &);
50
51namespace QtWaylandClient {
52
53static int alignTo(int input, int alignment)
54{
55 Q_ASSERT(alignment > 0);
56 if (int remainder = input % alignment)
57 return input + (alignment - remainder);
58 else
59 return input;
60}
61
65{
66 // This alignment of stride and size is done to improve performance of
67 // buffer accesses on the compositor side.
68 // Aligning the size of the shm pool to pages means the buffer can be
69 // imported as a udmabuf, and if the stride is additionally compatible with
70 // the GPU, that udmabuf can be used directly for rendering instead of needing
71 // to first copy to a GPU-accessible buffer.
72 // The 256 bytes stride alignment used here is what all common GPUs can read from.
73 const int stride = alignTo(size.width() * 4, 256);
74 const int alloc = alignTo(stride * size.height(), getpagesize());
75 int fd = -1;
76
77#ifdef SYS_memfd_create
79 if (fd >= 0)
81#endif
82
84 bool opened;
85
86 if (fd == -1) {
87 auto tmpFile =
89 QLatin1String("/wayland-shm-XXXXXX"));
90 opened = tmpFile->open();
92 } else {
93 auto file = std::make_unique<QFile>();
96 }
97 // NOTE beginPaint assumes a new buffer be all zeroes, which QFile::resize does.
98 if (!opened || !filePointer->resize(alloc)) {
99 qWarning("QWaylandShmBuffer: failed: %s", qUtf8Printable(filePointer->errorString()));
100 return;
101 }
102 fd = filePointer->handle();
103
104 // map ourselves: QFile::map() will unmap when the object is destroyed,
105 // but we want this mapping to persist (unmapping in destructor)
106 uchar *data = (uchar *)
107 mmap(nullptr, alloc, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
108 if (data == (uchar *) MAP_FAILED) {
109 qErrnoWarning("QWaylandShmBuffer: mmap failed");
110 return;
111 }
112
117
120 stride, wl_format));
122 wl_proxy_set_queue(reinterpret_cast<struct wl_proxy *>(buffer()), customEventQueue);
123}
124
133
135{
137
138 if (!margins.isNull() && margins != mMargins) {
139 if (mMarginsImage) {
140 delete mMarginsImage;
141 }
142 uchar *bits = const_cast<uchar *>(mImage.constBits());
148 }
149 if (margins.isNull()) {
150 delete mMarginsImage;
151 mMarginsImage = nullptr;
152 }
153
155 if (!mMarginsImage)
156 return &mImage;
157
158 return mMarginsImage;
159
160}
161
165{
170 auto copy = mBuffers;
171 // clear available buffers so we create new ones
172 // actual deletion is deferred till after resize call so we can copy
173 // contents from the back buffer
174 mBuffers.clear();
175 mFrontBuffer = nullptr;
176 // recreateBackBufferIfNeeded always resets mBackBuffer
179 else
180 mBackBuffer = nullptr;
183 });
184}
185
187{
189 w->setBackingStore(nullptr);
190
191// if (mFrontBuffer == waylandWindow()->attached())
192// waylandWindow()->attach(0);
193
196}
197
202
204{
205 // Update dirty state of buffers based on what was painted. The back buffer will be
206 // less dirty, since we painted to it, while other buffers will become more dirty.
207 // This allows us to minimize copies between front and back buffers on swap in the
208 // cases where the painted region overlaps with the previous frame (front buffer).
210 if (b != mBackBuffer)
211 b->dirtyRegion() += region;
212 else
213 b->dirtyRegion() -= region;
214 }
215}
216
218{
219 mPainting = true;
221
226
227 // Although undocumented, QBackingStore::beginPaint expects the painted region
228 // to be cleared before use if the window has a surface format with an alpha.
229 // Fresh QWaylandShmBuffer are already cleared, so we don't need to clear those.
230 if (!bufferWasRecreated && window()->format().hasAlpha()) {
233 const QColor blank = Qt::transparent;
234 for (const QRect &rect : region)
236 }
237}
238
240{
241 mPainting = false;
242 if (mPendingFlush)
244}
245
246// Inspired by QCALayerBackingStore.
248{
250 return false;
251
253
254 // On Wayland, the window can have a device pixel ratio different from
255 // the window/screen, therefore we cannot rely on QHighDpi here, cf. QBackingStore::scroll.
256 // With fractional scaling we cannot easily scroll the existing pixels.
257 if (!qFuzzyIsNull(devicePixelRatio - static_cast<int>(devicePixelRatio)))
258 return false;
259
261 if (!mFrontBuffer)
262 return false;
263
264 const QPoint scrollDelta(dx, dy);
267
270
271 if (!inPlaceRegion.isEmpty()) {
274
279 }
280
281 if (!frontBufferRegion.isEmpty()) {
285 for (const QRect &rect : frontBufferRegion) {
291 }
292 }
293
294 // We do not mark the source region as dirty, even though it technically has "moved".
295 // This matches the behavior of other backingstore implementations using qt_scrollRectInImage.
297
298 return true;
299}
300
302{
303 // Invoked when the window is of type RasterSurface.
304
305 if (window != this->window()) {
306 auto waylandWindow = static_cast<QWaylandWindow *>(window->handle());
307 const auto scale = waylandWindow->scale();
308 auto newBuffer = new QWaylandShmBuffer(
316 return;
317 }
318
319 if (mPainting) {
321 mPendingFlush = true;
322 return;
323 }
324
325 mPendingFlush = false;
327
330
332
334
337}
338
340{
342}
343
345{
346 static const int MAX_BUFFERS = 5;
347 static const int MAX_AGE = 10 * MAX_BUFFERS;
348 bufferWasRecreated = false;
349
350 // Prune buffers that have not been used in a while or with different size.
351 for (auto i = mBuffers.size() - 1; i >= 0; --i) {
353 if (buffer->age() > MAX_AGE || buffer->size() != size) {
355 if (mBackBuffer == buffer)
356 mBackBuffer = nullptr;
357 if (mFrontBuffer == buffer)
358 mFrontBuffer = nullptr;
359 delete buffer;
360 }
361 }
362
363 QWaylandShmBuffer *buffer = nullptr;
365 if (candidate->busy())
366 continue;
367
368 if (!buffer || candidate->age() < buffer->age())
370 }
371
372 if (buffer)
373 return buffer;
374
375 if (mBuffers.size() < MAX_BUFFERS) {
377 if (!waylandWindow()->format().hasAlpha())
380 bufferWasRecreated = true;
382 return b;
383 }
384 return nullptr;
385}
386
388{
390
391 bool bufferWasRecreated = false;
395
396 // We look for a free buffer to draw into. If the buffer is not the last buffer we used,
397 // that is mBackBuffer, and the size is the same we copy the damaged content into the new
398 // buffer so that QPainter is happy to find the stuff it had drawn before. If the new
399 // buffer has a different size it needs to be redrawn completely anyway, and if the buffer
400 // is the same the stuff is there already.
401 // You can exercise the different codepaths with weston, switching between the gl and the
402 // pixman renderer. With the gl renderer release events are sent early so we can effectively
403 // run single buffered, while with the pixman renderer we have to use two.
405 while (!buffer) {
406 struct ::wl_display *display = mDisplay->wl_display();
409 if ((ecode == EPIPE || ecode == ECONNRESET))
410 qWarning("The Wayland connection broke during blocking read event. Did the Wayland compositor die?");
411 else
412 qWarning("The Wayland connection experienced a fatal error during blocking read event: %s", strerror(ecode));
413 _exit(-1);
414 }
416 }
417
420
422
424 if (mBackBuffer == buffer) {
425 buffer->setAge(0);
426 } else {
427 buffer->setAge(buffer->age() + 1);
428 }
429 }
430
433
434 return bufferWasRecreated;
435}
436
438{
440
442 if (clipRegion.isEmpty())
443 return;
444
446 return;
447
450
454 const auto clipRects = clipRegion.rects();
455 for (const QRect &clipRect : clipRects) { // Iterate clip rects, because complicated clip region causes higher CPU usage
456 if (clipRects.size() > 1)
457 painter.save();
461 if (clipRects.size() > 1)
463 }
464
466}
467
472
477
479{
483
485 int dpWidth = int(sourceImage.width() / dp);
486 int dpHeight = int(sourceImage.height() / dp);
489 QRect target; // needs to be in device independent pixels
491
492 //Top
493 target.setX(0);
494 target.setY(0);
499
500 //Left
505
506 //Right
511
512 //Bottom
513 target.setX(0);
519
521}
522
527
534
536{
537 return static_cast<QWaylandWindow *>(window()->handle());
538}
539
540#if QT_CONFIG(opengl)
542{
543 // Invoked from QPlatformBackingStore::composeAndFlush() that is called
544 // instead of flush() for widgets that have renderToTexture children
545 // (QOpenGLWidget, QQuickWidget).
546
547 const_cast<QWaylandShmBackingStore *>(this)->finalizeBackBuffer();
548
549 return *contentSurface();
550}
551#endif // opengl
552
553}
554
555QT_END_NAMESPACE
static int alignTo(int input, int alignment)
#define MAP_FAILED
QT_BEGIN_NAMESPACE void qt_scrollRectInImage(QImage &, const QRect &, const QPoint &)