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