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
132
136{
141 auto copy = mBuffers;
142 // clear available buffers so we create new ones
143 // actual deletion is deferred till after resize call so we can copy
144 // contents from the back buffer
145 mBuffers.clear();
146 mFrontBuffer = nullptr;
147 // recreateBackBufferIfNeeded always resets mBackBuffer
150 else
151 mBackBuffer = nullptr;
154 });
155}
156
158{
160 w->setBackingStore(nullptr);
161
162// if (mFrontBuffer == waylandWindow()->attached())
163// waylandWindow()->attach(0);
164
167}
168
173
175{
176 // Update dirty state of buffers based on what was painted. The back buffer will be
177 // less dirty, since we painted to it, while other buffers will become more dirty.
178 // This allows us to minimize copies between front and back buffers on swap in the
179 // cases where the painted region overlaps with the previous frame (front buffer).
181 if (b != mBackBuffer)
182 b->dirtyRegion() += region;
183 else
184 b->dirtyRegion() -= region;
185 }
186}
187
189{
190 mPainting = true;
194
195 // Although undocumented, QBackingStore::beginPaint expects the painted region
196 // to be cleared before use if the window has a surface format with an alpha.
197 // Fresh QWaylandShmBuffer are already cleared, so we don't need to clear those.
198 if (!bufferWasRecreated && window()->format().hasAlpha()) {
201 const QColor blank = Qt::transparent;
202 for (const QRect &rect : region)
204 }
205}
206
208{
209 mPainting = false;
210 if (mPendingFlush)
212}
213
214// Inspired by QCALayerBackingStore.
216{
218 return false;
219
221
222 // On Wayland, the window can have a device pixel ratio different from
223 // the window/screen, therefore we cannot rely on QHighDpi here, cf. QBackingStore::scroll.
224 // With fractional scaling we cannot easily scroll the existing pixels.
225 if (!qFuzzyIsNull(devicePixelRatio - static_cast<int>(devicePixelRatio)))
226 return false;
227
229 if (!mFrontBuffer)
230 return false;
231
232 const QPoint scrollDelta(dx, dy);
233
236
237 if (!inPlaceRegion.isEmpty()) {
240
245 }
246
247 if (!frontBufferRegion.isEmpty()) {
251 for (const QRect &rect : frontBufferRegion) {
257 }
258 }
259
260 // We do not mark the source region as dirty, even though it technically has "moved".
261 // This matches the behavior of other backingstore implementations using qt_scrollRectInImage.
263
264 return true;
265}
266
268{
269 // Invoked when the window is of type RasterSurface.
270
271 if (window != this->window()) {
272 auto waylandWindow = static_cast<QWaylandWindow *>(window->handle());
273 const auto scale = waylandWindow->scale();
274 auto newBuffer = new QWaylandShmBuffer(
282 return;
283 }
284
285 if (mPainting) {
287 mPendingFlush = true;
288 return;
289 }
290
291 mPendingFlush = false;
293
295
297
299}
300
302{
304}
305
307{
308 static const int MAX_BUFFERS = 5;
309 static const int MAX_AGE = 10 * MAX_BUFFERS;
310 bufferWasRecreated = false;
311
312 // Prune buffers that have not been used in a while. The front buffer must not be touched
313 // so its data can be copied to new buffers. The back buffer can be pruned but we don't
314 // do it because we will probably need it anyway if the front buffer is used by the compositor.
315 for (auto i = mBuffers.size() - 1; i >= 0; --i) {
317 if (mBackBuffer == buffer)
318 continue;
319 if (mFrontBuffer == buffer)
320 continue;
321
322 if (buffer->age() > MAX_AGE) {
324 delete buffer;
325 }
326 }
327
328 // Prune buffers that have a mismatching size or device pixel ratio. A buffer must not be
329 // reused across a scale change: its ratio is what maps logical coordinates to device
330 // pixels, and the requested pixel size carries no information about the scale (a surface
331 // whose physical size is pinned requests the same pixel size at every scale, so the scale
332 // cancels out of the size calculation).
333 //
334 // Buffers still held by the compositor are only stripped of their front/back buffer
335 // role and are destroyed once released. Destroying a wl_buffer before it has been
336 // released will make some compositors display nothing until the next commit, causing
337 // flickering.
338 const qreal scale = waylandWindow()->scale();
339 for (auto i = mBuffers.size() - 1; i >= 0; --i) {
341 if (buffer->size() != size
343 if (mBackBuffer == buffer)
344 mBackBuffer = nullptr;
345 if (mFrontBuffer == buffer)
346 mFrontBuffer = nullptr;
347 if (buffer->busy())
348 continue;
350 delete buffer;
351 }
352 }
353
354 QWaylandShmBuffer *buffer = nullptr;
356 if (candidate->busy())
357 continue;
358
359 if (!buffer || candidate->age() < buffer->age())
361 }
362
363 if (buffer)
364 return buffer;
365
366 if (mBuffers.size() < MAX_BUFFERS) {
368 if (!waylandWindow()->format().hasAlpha())
371 bufferWasRecreated = true;
373 return b;
374 }
375 return nullptr;
376}
377
379{
381
382 bool bufferWasRecreated = false;
385
386 // We look for a free buffer to draw into. If the buffer is not the last buffer we used,
387 // that is mBackBuffer, and the size is the same we copy the damaged content into the new
388 // buffer so that QPainter is happy to find the stuff it had drawn before. If the new
389 // buffer has a different size it needs to be redrawn completely anyway, and if the buffer
390 // is the same the stuff is there already.
391 // You can exercise the different codepaths with weston, switching between the gl and the
392 // pixman renderer. With the gl renderer release events are sent early so we can effectively
393 // run single buffered, while with the pixman renderer we have to use two.
395 while (!buffer) {
396 struct ::wl_display *display = mDisplay->wl_display();
399 if ((ecode == EPIPE || ecode == ECONNRESET))
400 qWarning("The Wayland connection broke during blocking read event. Did the Wayland compositor die?");
401 else
402 qWarning("The Wayland connection experienced a fatal error during blocking read event: %s", strerror(ecode));
403 _exit(-1);
404 }
406 }
407
409
411 if (mBackBuffer == buffer) {
412 buffer->setAge(0);
413 } else {
414 buffer->setAge(buffer->age() + 1);
415 }
416 }
417
418 return bufferWasRecreated;
419}
420
422{
424
426 if (clipRegion.isEmpty())
427 return;
428
430 return;
431
434
438 const auto clipRects = clipRegion.rects();
439 for (const QRect &clipRect : clipRects) { // Iterate clip rects, because complicated clip region causes higher CPU usage
440 if (clipRects.size() > 1)
441 painter.save();
445 if (clipRects.size() > 1)
447 }
448
450}
451
456
458{
459 return static_cast<QWaylandWindow *>(window()->handle());
460}
461
462#if QT_CONFIG(opengl)
464{
465 // Invoked from QPlatformBackingStore::composeAndFlush() that is called
466 // instead of flush() for widgets that have renderToTexture children
467 // (QOpenGLWidget, QQuickWidget).
468
469 const_cast<QWaylandShmBackingStore *>(this)->finalizeBackBuffer();
470
471 return *contentSurface();
472}
473#endif // opengl
474
475}
476
477QT_END_NAMESPACE
static int alignTo(int input, int alignment)
#define MAP_FAILED
QT_BEGIN_NAMESPACE void qt_scrollRectInImage(QImage &, const QRect &, const QPoint &)