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
46QT_BEGIN_NAMESPACE
47
48namespace QtWaylandClient {
49
50QWaylandShmBuffer::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
104QWaylandShmBuffer::~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
113QImage *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
141QWaylandShmBackingStore::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
159QWaylandShmBackingStore::~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
170QPaintDevice *QWaylandShmBackingStore::paintDevice()
171{
172 return contentSurface();
173}
174
175void QWaylandShmBackingStore::updateDirtyStates(const QRegion &region)
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
185void QWaylandShmBackingStore::beginPaint(const QRegion &region)
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
206void QWaylandShmBackingStore::endPaint()
207{
208 mPainting = false;
209 if (mPendingFlush)
210 flush(window: window(), region: mPendingRegion, offset: QPoint());
211}
212
213void QWaylandShmBackingStore::flush(QWindow *window, const QRegion &region, 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
251void QWaylandShmBackingStore::resize(const QSize &size, const QRegion &)
252{
253 mRequestedSize = size;
254}
255
256QWaylandShmBuffer *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
285bool 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
345QImage *QWaylandShmBackingStore::entireSurface() const
346{
347 return mBackBuffer->image();
348}
349
350QImage *QWaylandShmBackingStore::contentSurface() const
351{
352 return windowDecoration() ? mBackBuffer->imageInsideMargins(marginsIn: windowDecorationMargins()) : mBackBuffer->image();
353}
354
355void 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
400QWaylandAbstractDecoration *QWaylandShmBackingStore::windowDecoration() const
401{
402 return waylandWindow()->decoration();
403}
404
405QMargins QWaylandShmBackingStore::windowDecorationMargins() const
406{
407 if (windowDecoration())
408 return windowDecoration()->margins();
409 return QMargins();
410}
411
412QWaylandWindow *QWaylandShmBackingStore::waylandWindow() const
413{
414 return static_cast<QWaylandWindow *>(window()->handle());
415}
416
417#if QT_CONFIG(opengl)
418QImage 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
430QT_END_NAMESPACE
431

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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