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

source code of qtwayland/src/client/qwaylandshmbackingstore.cpp