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 : 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
100QWaylandShmBuffer::~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
109QImage *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
137QWaylandShmBackingStore::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
155QWaylandShmBackingStore::~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
166QPaintDevice *QWaylandShmBackingStore::paintDevice()
167{
168 return contentSurface();
169}
170
171void QWaylandShmBackingStore::updateDirtyStates(const QRegion &region)
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
181void QWaylandShmBackingStore::beginPaint(const QRegion &region)
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
202void QWaylandShmBackingStore::endPaint()
203{
204 mPainting = false;
205 if (mPendingFlush)
206 flush(window: window(), region: mPendingRegion, offset: QPoint());
207}
208
209void QWaylandShmBackingStore::flush(QWindow *window, const QRegion &region, 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
247void QWaylandShmBackingStore::resize(const QSize &size, const QRegion &)
248{
249 mRequestedSize = size;
250}
251
252QWaylandShmBuffer *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
281bool 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
347QImage *QWaylandShmBackingStore::entireSurface() const
348{
349 return mBackBuffer->image();
350}
351
352QImage *QWaylandShmBackingStore::contentSurface() const
353{
354 return windowDecoration() ? mBackBuffer->imageInsideMargins(marginsIn: windowDecorationMargins()) : mBackBuffer->image();
355}
356
357void 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
402QWaylandAbstractDecoration *QWaylandShmBackingStore::windowDecoration() const
403{
404 return waylandWindow()->decoration();
405}
406
407QMargins QWaylandShmBackingStore::windowDecorationMargins() const
408{
409 if (windowDecoration())
410 return windowDecoration()->margins();
411 return QMargins();
412}
413
414QWaylandWindow *QWaylandShmBackingStore::waylandWindow() const
415{
416 return static_cast<QWaylandWindow *>(window()->handle());
417}
418
419#if QT_CONFIG(opengl)
420QImage 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
432QT_END_NAMESPACE
433

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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