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