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 | |
44 | QT_BEGIN_NAMESPACE |
45 | |
46 | namespace QtWaylandClient { |
47 | |
48 | QWaylandShmBuffer::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 | |
98 | QWaylandShmBuffer::~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 | |
107 | QImage *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 | |
135 | QWaylandShmBackingStore::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 | |
153 | QWaylandShmBackingStore::~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 | |
164 | QPaintDevice *QWaylandShmBackingStore::paintDevice() |
165 | { |
166 | return contentSurface(); |
167 | } |
168 | |
169 | void QWaylandShmBackingStore::beginPaint(const QRegion ®ion) |
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 | |
185 | void QWaylandShmBackingStore::endPaint() |
186 | { |
187 | mPainting = false; |
188 | if (mPendingFlush) |
189 | flush(window: window(), region: mPendingRegion, offset: QPoint()); |
190 | waylandWindow()->setCanResize(true); |
191 | } |
192 | |
193 | void QWaylandShmBackingStore::ensureSize() |
194 | { |
195 | waylandWindow()->setBackingStore(this); |
196 | waylandWindow()->createDecoration(); |
197 | resize(size: mRequestedSize); |
198 | } |
199 | |
200 | void QWaylandShmBackingStore::flush(QWindow *window, const QRegion ®ion, 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 | |
231 | void QWaylandShmBackingStore::resize(const QSize &size, const QRegion &) |
232 | { |
233 | mRequestedSize = size; |
234 | } |
235 | |
236 | QWaylandShmBuffer *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 | |
262 | void 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 | |
304 | QImage *QWaylandShmBackingStore::entireSurface() const |
305 | { |
306 | return mBackBuffer->image(); |
307 | } |
308 | |
309 | QImage *QWaylandShmBackingStore::contentSurface() const |
310 | { |
311 | return windowDecoration() ? mBackBuffer->imageInsideMargins(marginsIn: windowDecorationMargins()) : mBackBuffer->image(); |
312 | } |
313 | |
314 | void 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 | |
352 | QWaylandAbstractDecoration *QWaylandShmBackingStore::windowDecoration() const |
353 | { |
354 | return waylandWindow()->decoration(); |
355 | } |
356 | |
357 | QMargins QWaylandShmBackingStore::windowDecorationMargins() const |
358 | { |
359 | if (windowDecoration()) |
360 | return windowDecoration()->margins(); |
361 | return QMargins(); |
362 | } |
363 | |
364 | QWaylandWindow *QWaylandShmBackingStore::waylandWindow() const |
365 | { |
366 | return static_cast<QWaylandWindow *>(window()->handle()); |
367 | } |
368 | |
369 | #if QT_CONFIG(opengl) |
370 | QImage 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 | |
382 | QT_END_NAMESPACE |
383 | |