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. The front buffer must not be touched
351 // so its data can be copied to new buffers. The back buffer can be pruned but we don't
352 // do it because we will probably need it anyway if the front buffer is used by the compositor.
353 for (auto i = mBuffers.size() - 1; i >= 0; --i) {
355 if (mBackBuffer == buffer)
356 continue;
357 if (mFrontBuffer == buffer)
358 continue;
359
360 if (buffer->age() > MAX_AGE) {
362 delete buffer;
363 }
364 }
365
366 // Prune buffers that have mismatching size.
367 for (auto i = mBuffers.size() - 1; i >= 0; --i) {
369 if (buffer->size() != size) {
371 if (mBackBuffer == buffer)
372 mBackBuffer = nullptr;
373 if (mFrontBuffer == buffer)
374 mFrontBuffer = nullptr;
375 delete buffer;
376 }
377 }
378
379 QWaylandShmBuffer *buffer = nullptr;
381 if (candidate->busy())
382 continue;
383
384 if (!buffer || candidate->age() < buffer->age())
386 }
387
388 if (buffer)
389 return buffer;
390
391 if (mBuffers.size() < MAX_BUFFERS) {
393 if (!waylandWindow()->format().hasAlpha())
396 bufferWasRecreated = true;
398 return b;
399 }
400 return nullptr;
401}
402
404{
406
407 bool bufferWasRecreated = false;
411
412 // We look for a free buffer to draw into. If the buffer is not the last buffer we used,
413 // that is mBackBuffer, and the size is the same we copy the damaged content into the new
414 // buffer so that QPainter is happy to find the stuff it had drawn before. If the new
415 // buffer has a different size it needs to be redrawn completely anyway, and if the buffer
416 // is the same the stuff is there already.
417 // You can exercise the different codepaths with weston, switching between the gl and the
418 // pixman renderer. With the gl renderer release events are sent early so we can effectively
419 // run single buffered, while with the pixman renderer we have to use two.
421 while (!buffer) {
422 struct ::wl_display *display = mDisplay->wl_display();
425 if ((ecode == EPIPE || ecode == ECONNRESET))
426 qWarning("The Wayland connection broke during blocking read event. Did the Wayland compositor die?");
427 else
428 qWarning("The Wayland connection experienced a fatal error during blocking read event: %s", strerror(ecode));
429 _exit(-1);
430 }
432 }
433
436
438
440 if (mBackBuffer == buffer) {
441 buffer->setAge(0);
442 } else {
443 buffer->setAge(buffer->age() + 1);
444 }
445 }
446
449
450 return bufferWasRecreated;
451}
452
454{
456
458 if (clipRegion.isEmpty())
459 return;
460
462 return;
463
466
470 const auto clipRects = clipRegion.rects();
471 for (const QRect &clipRect : clipRects) { // Iterate clip rects, because complicated clip region causes higher CPU usage
472 if (clipRects.size() > 1)
473 painter.save();
477 if (clipRects.size() > 1)
479 }
480
482}
483
488
493
495{
499
501 int dpWidth = int(sourceImage.width() / dp);
502 int dpHeight = int(sourceImage.height() / dp);
505 QRect target; // needs to be in device independent pixels
507
508 //Top
509 target.setX(0);
510 target.setY(0);
515
516 //Left
521
522 //Right
527
528 //Bottom
529 target.setX(0);
535
537}
538
543
550
552{
553 return static_cast<QWaylandWindow *>(window()->handle());
554}
555
556#if QT_CONFIG(opengl)
558{
559 // Invoked from QPlatformBackingStore::composeAndFlush() that is called
560 // instead of flush() for widgets that have renderToTexture children
561 // (QOpenGLWidget, QQuickWidget).
562
563 const_cast<QWaylandShmBackingStore *>(this)->finalizeBackBuffer();
564
565 return *contentSurface();
566}
567#endif // opengl
568
569}
570
571QT_END_NAMESPACE
static int alignTo(int input, int alignment)
#define MAP_FAILED
QT_BEGIN_NAMESPACE void qt_scrollRectInImage(QImage &, const QRect &, const QPoint &)