Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Toggle main menu visibility
Loading...
Searching...
No Matches
qwaylandshmbackingstore.cpp
Go to the documentation of this file.
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
// Qt-Security score:significant reason:default
4
#
include
"qwaylandshmbackingstore_p.h"
5
#
include
"qwaylandwindow_p.h"
6
#
include
"qwaylandsubsurface_p.h"
7
#
include
"qwaylanddisplay_p.h"
8
#
include
"qwaylandscreen_p.h"
9
#
include
"qwaylandabstractdecoration_p.h"
10
11
#
include
<
QtCore
/
qdebug
.
h
>
12
#
include
<
QtCore
/
qstandardpaths
.
h
>
13
#
include
<
QtCore
/
qtemporaryfile
.
h
>
14
#
include
<
QtGui
/
QPainter
>
15
#
include
<
QtGui
/
QTransform
>
16
#
include
<
QMutexLocker
>
17
18
#
include
<
QtWaylandClient
/
private
/
wayland
-
wayland
-
client
-
protocol
.
h
>
19
20
#
include
<
memory
>
21
22
#
include
<
fcntl
.
h
>
23
#
include
<
unistd
.
h
>
24
#
include
<
sys
/
mman
.
h
>
25
26
#
ifdef
Q_OS_LINUX
27
#
include
<
sys
/
syscall
.
h
>
28
// from linux/memfd.h:
29
#
ifndef
MFD_CLOEXEC
30
#
define
MFD_CLOEXEC
0x0001U
31
#
endif
32
#
ifndef
MFD_ALLOW_SEALING
33
#
define
MFD_ALLOW_SEALING
0x0002U
34
#
endif
35
// from bits/fcntl-linux.h
36
#
ifndef
F_ADD_SEALS
37
#
define
F_ADD_SEALS
1033
38
#
endif
39
#
ifndef
F_SEAL_SEAL
40
#
define
F_SEAL_SEAL
0x0001
41
#
endif
42
#
ifndef
F_SEAL_SHRINK
43
#
define
F_SEAL_SHRINK
0x0002
44
#
endif
45
#
endif
46
47
QT_BEGIN_NAMESPACE
48
49
extern
void
qt_scrollRectInImage
(QImage &,
const
QRect &,
const
QPoint &);
50
51
namespace
QtWaylandClient
{
52
53
static
int
alignTo
(
int
input,
int
alignment)
54
{
55
Q_ASSERT(alignment > 0);
56
if
(
int
remainder = input % alignment)
57
return
input + (alignment - remainder);
58
else
59
return
input;
60
}
61
62
QWaylandShmBuffer
::
QWaylandShmBuffer
(
QWaylandDisplay
*
display
,
63
const
QSize
&
size
,
QImage
::
Format
format
,
qreal
scale
,
wl_event_queue
*
customEventQueue
)
64
:
mDirtyRegion
(
QRect
(
QPoint
(0, 0),
size
/
scale
))
65
{
66
// This alignment of stride and size is done to improve performance of
67
// buffer accesses on the compositor side.
68
// Aligning the size of the shm pool to pages means the buffer can be
69
// imported as a udmabuf, and if the stride is additionally compatible with
70
// the GPU, that udmabuf can be used directly for rendering instead of needing
71
// to first copy to a GPU-accessible buffer.
72
// The 256 bytes stride alignment used here is what all common GPUs can read from.
73
const
int
stride
=
alignTo
(
size
.
width
() * 4, 256);
74
const
int
alloc
=
alignTo
(
stride
*
size
.
height
(),
getpagesize
());
75
int
fd
= -1;
76
77
#
ifdef
SYS_memfd_create
78
fd
=
syscall
(
SYS_memfd_create
,
"wayland-shm"
,
MFD_CLOEXEC
|
MFD_ALLOW_SEALING
);
79
if
(
fd
>= 0)
80
fcntl
(
fd
,
F_ADD_SEALS
,
F_SEAL_SHRINK
|
F_SEAL_SEAL
);
81
#
endif
82
83
std
::
unique_ptr
<
QFile
>
filePointer
;
84
bool
opened
;
85
86
if
(
fd
== -1) {
87
auto
tmpFile
=
88
std
::
make_unique
<
QTemporaryFile
>(
QStandardPaths
::
writableLocation
(
QStandardPaths
::
RuntimeLocation
) +
89
QLatin1String
(
"/wayland-shm-XXXXXX"
));
90
opened
=
tmpFile
->
open
();
91
filePointer
=
std
::
move
(
tmpFile
);
92
}
else
{
93
auto
file
=
std
::
make_unique
<
QFile
>();
94
opened
=
file
->
open
(
fd
,
QIODevice
::
ReadWrite
|
QIODevice
::
Unbuffered
,
QFile
::
AutoCloseHandle
);
95
filePointer
=
std
::
move
(
file
);
96
}
97
// NOTE beginPaint assumes a new buffer be all zeroes, which QFile::resize does.
98
if
(!
opened
|| !
filePointer
->
resize
(
alloc
)) {
99
qWarning
(
"QWaylandShmBuffer: failed: %s"
,
qUtf8Printable
(
filePointer
->
errorString
()));
100
return
;
101
}
102
fd
=
filePointer
->
handle
();
103
104
// map ourselves: QFile::map() will unmap when the object is destroyed,
105
// but we want this mapping to persist (unmapping in destructor)
106
uchar
*
data
= (
uchar
*)
107
mmap
(
nullptr
,
alloc
, PROT_READ | PROT_WRITE, MAP_SHARED,
fd
, 0);
108
if
(
data
== (
uchar
*)
MAP_FAILED
) {
109
qErrnoWarning
(
"QWaylandShmBuffer: mmap failed"
);
110
return
;
111
}
112
113
QWaylandShm
*
shm
=
display
->
shm
();
114
wl_shm_format
wl_format
=
shm
->
formatFrom
(
format
);
115
mImage
=
QImage
(
data
,
size
.
width
(),
size
.
height
(),
stride
,
format
);
116
mImage
.
setDevicePixelRatio
(
scale
);
117
118
mShmPool
=
wl_shm_create_pool
(
shm
->
object
(),
fd
,
alloc
);
119
init
(
wl_shm_pool_create_buffer
(
mShmPool
,0,
size
.
width
(),
size
.
height
(),
120
stride
,
wl_format
));
121
if
(
customEventQueue
)
122
wl_proxy_set_queue
(
reinterpret_cast
<
struct
wl_proxy
*>(
buffer
()),
customEventQueue
);
123
}
124
125
QWaylandShmBuffer
::~
QWaylandShmBuffer
(
void
)
126
{
127
if
(
mImage
.
constBits
())
128
munmap
((
void
*)
mImage
.
constBits
(),
mImage
.
sizeInBytes
());
129
if
(
mShmPool
)
130
wl_shm_pool_destroy
(
mShmPool
);
131
}
132
133
QWaylandShmBackingStore
::
QWaylandShmBackingStore
(
QWindow
*
window
,
QWaylandDisplay
*
display
)
134
:
QPlatformBackingStore
(
window
)
135
,
mDisplay
(
display
)
136
{
137
mEventQueue
=
wl_display_create_queue
(
mDisplay
->
wl_display
());
138
QObject
::
connect
(
mDisplay
, &
QWaylandDisplay
::
connected
,
window
, [
this
]() {
139
auto
oldEventQueue
=
mEventQueue
;
140
mEventQueue
=
wl_display_create_queue
(
mDisplay
->
wl_display
());
141
auto
copy
=
mBuffers
;
142
// clear available buffers so we create new ones
143
// actual deletion is deferred till after resize call so we can copy
144
// contents from the back buffer
145
mBuffers
.
clear
();
146
mFrontBuffer
=
nullptr
;
147
// recreateBackBufferIfNeeded always resets mBackBuffer
148
if
(
mRequestedSize
.
isValid
() &&
waylandWindow
())
149
recreateBackBufferIfNeeded
();
150
else
151
mBackBuffer
=
nullptr
;
152
qDeleteAll
(
copy
);
153
wl_event_queue_destroy
(
oldEventQueue
);
154
});
155
}
156
157
QWaylandShmBackingStore
::~
QWaylandShmBackingStore
()
158
{
159
if
(
QWaylandWindow
*
w
=
waylandWindow
())
160
w
->
setBackingStore
(
nullptr
);
161
162
// if (mFrontBuffer == waylandWindow()->attached())
163
// waylandWindow()->attach(0);
164
165
qDeleteAll
(
mBuffers
);
166
wl_event_queue_destroy
(
mEventQueue
);
167
}
168
169
QPaintDevice
*
QWaylandShmBackingStore
::
paintDevice
()
170
{
171
return
contentSurface
();
172
}
173
174
void
QWaylandShmBackingStore
::
updateDirtyStates
(
const
QRegion
&
region
)
175
{
176
// Update dirty state of buffers based on what was painted. The back buffer will be
177
// less dirty, since we painted to it, while other buffers will become more dirty.
178
// This allows us to minimize copies between front and back buffers on swap in the
179
// cases where the painted region overlaps with the previous frame (front buffer).
180
for
(
QWaylandShmBuffer
*
b
:
std
::
as_const
(
mBuffers
)) {
181
if
(
b
!=
mBackBuffer
)
182
b
->
dirtyRegion
() +=
region
;
183
else
184
b
->
dirtyRegion
() -=
region
;
185
}
186
}
187
188
void
QWaylandShmBackingStore
::
beginPaint
(
const
QRegion
&
region
)
189
{
190
mPainting
=
true
;
191
waylandWindow
()->
setBackingStore
(
this
);
192
const
bool
bufferWasRecreated
=
recreateBackBufferIfNeeded
();
193
updateDirtyStates
(
region
);
194
195
// Although undocumented, QBackingStore::beginPaint expects the painted region
196
// to be cleared before use if the window has a surface format with an alpha.
197
// Fresh QWaylandShmBuffer are already cleared, so we don't need to clear those.
198
if
(!
bufferWasRecreated
&&
window
()->
format
().
hasAlpha
()) {
199
QPainter
p
(
paintDevice
());
200
p
.
setCompositionMode
(
QPainter
::
CompositionMode_Source
);
201
const
QColor
blank
=
Qt
::
transparent
;
202
for
(
const
QRect
&
rect
:
region
)
203
p
.
fillRect
(
rect
,
blank
);
204
}
205
}
206
207
void
QWaylandShmBackingStore
::
endPaint
()
208
{
209
mPainting
=
false
;
210
if
(
mPendingFlush
)
211
flush
(
window
(),
mPendingRegion
,
QPoint
());
212
}
213
214
// Inspired by QCALayerBackingStore.
215
bool
QWaylandShmBackingStore
::
scroll
(
const
QRegion
&
region
,
int
dx
,
int
dy
)
216
{
217
if
(
Q_UNLIKELY
(!
mBackBuffer
))
218
return
false
;
219
220
const
qreal
devicePixelRatio
=
waylandWindow
()->
scale
();
221
222
// On Wayland, the window can have a device pixel ratio different from
223
// the window/screen, therefore we cannot rely on QHighDpi here, cf. QBackingStore::scroll.
224
// With fractional scaling we cannot easily scroll the existing pixels.
225
if
(!
qFuzzyIsNull
(
devicePixelRatio
-
static_cast
<
int
>(
devicePixelRatio
)))
226
return
false
;
227
228
recreateBackBufferIfNeeded
();
229
if
(!
mFrontBuffer
)
230
return
false
;
231
232
const
QPoint
scrollDelta
(
dx
,
dy
);
233
234
const
QRegion
inPlaceRegion
=
region
-
mBackBuffer
->
dirtyRegion
();
235
const
QRegion
frontBufferRegion
=
region
-
inPlaceRegion
;
236
237
if
(!
inPlaceRegion
.
isEmpty
()) {
238
const
QRect
inPlaceBoundingRect
=
inPlaceRegion
.
boundingRect
();
239
const
QPoint
devicePixelDelta
=
scrollDelta
*
devicePixelRatio
;
240
241
qt_scrollRectInImage
(*
mBackBuffer
->
image
(),
242
QRect
(
inPlaceBoundingRect
.
topLeft
() *
devicePixelRatio
,
243
inPlaceBoundingRect
.
size
() *
devicePixelRatio
),
244
devicePixelDelta
);
245
}
246
247
if
(!
frontBufferRegion
.
isEmpty
()) {
248
QPainter
painter
(
mBackBuffer
->
image
());
249
painter
.
setCompositionMode
(
QPainter
::
CompositionMode_Source
);
250
painter
.
scale
(
qreal
(1) /
devicePixelRatio
,
qreal
(1) /
devicePixelRatio
);
251
for
(
const
QRect
&
rect
:
frontBufferRegion
) {
252
QRect
sourceRect
(
rect
.
topLeft
() *
devicePixelRatio
,
253
rect
.
size
() *
devicePixelRatio
);
254
QRect
destinationRect
((
rect
.
topLeft
() +
scrollDelta
) *
devicePixelRatio
,
255
rect
.
size
() *
devicePixelRatio
);
256
painter
.
drawImage
(
destinationRect
, *
mFrontBuffer
->
image
(),
sourceRect
);
257
}
258
}
259
260
// We do not mark the source region as dirty, even though it technically has "moved".
261
// This matches the behavior of other backingstore implementations using qt_scrollRectInImage.
262
updateDirtyStates
(
region
.
translated
(
scrollDelta
));
263
264
return
true
;
265
}
266
267
void
QWaylandShmBackingStore
::
flush
(
QWindow
*
window
,
const
QRegion
&
region
,
const
QPoint
&
offset
)
268
{
269
// Invoked when the window is of type RasterSurface.
270
271
if
(
window
!=
this
->
window
()) {
272
auto
waylandWindow
=
static_cast
<
QWaylandWindow
*>(
window
->
handle
());
273
const
auto
scale
=
waylandWindow
->
scale
();
274
auto
newBuffer
=
new
QWaylandShmBuffer
(
275
mDisplay
,
window
->
size
() *
scale
,
mBackBuffer
->
image
()->
format
(),
276
mBackBuffer
->
image
()->
devicePixelRatio
());
277
newBuffer
->
setDeleteOnRelease
(
true
);
278
QRect
sourceRect
(
offset
*
scale
,
window
->
size
() *
scale
);
279
QPainter
painter
(
newBuffer
->
image
());
280
painter
.
drawImage
(
QPoint
(0, 0), *
mBackBuffer
->
image
(),
sourceRect
);
281
waylandWindow
->
safeCommit
(
newBuffer
,
region
);
282
return
;
283
}
284
285
if
(
mPainting
) {
286
mPendingRegion
|=
region
;
287
mPendingFlush
=
true
;
288
return
;
289
}
290
291
mPendingFlush
=
false
;
292
mPendingRegion
=
QRegion
();
293
294
finalizeBackBuffer
();
295
296
mFrontBuffer
=
mBackBuffer
;
297
298
waylandWindow
()->
safeCommit
(
mFrontBuffer
,
region
);
299
}
300
301
void
QWaylandShmBackingStore
::
resize
(
const
QSize
&
size
,
const
QRegion
&)
302
{
303
mRequestedSize
=
size
;
304
}
305
306
QWaylandShmBuffer
*
QWaylandShmBackingStore
::
getBuffer
(
const
QSize
&
size
,
bool
&
bufferWasRecreated
)
307
{
308
static
const
int
MAX_BUFFERS
= 5;
309
static
const
int
MAX_AGE
= 10 *
MAX_BUFFERS
;
310
bufferWasRecreated
=
false
;
311
312
// Prune buffers that have not been used in a while. The front buffer must not be touched
313
// so its data can be copied to new buffers. The back buffer can be pruned but we don't
314
// do it because we will probably need it anyway if the front buffer is used by the compositor.
315
for
(
auto
i
=
mBuffers
.
size
() - 1;
i
>= 0; --
i
) {
316
QWaylandShmBuffer
*
buffer
=
mBuffers
[
i
];
317
if
(
mBackBuffer
==
buffer
)
318
continue
;
319
if
(
mFrontBuffer
==
buffer
)
320
continue
;
321
322
if
(
buffer
->
age
() >
MAX_AGE
) {
323
mBuffers
.
removeAt
(
i
);
324
delete
buffer
;
325
}
326
}
327
328
// Prune buffers that have a mismatching size or device pixel ratio. A buffer must not be
329
// reused across a scale change: its ratio is what maps logical coordinates to device
330
// pixels, and the requested pixel size carries no information about the scale (a surface
331
// whose physical size is pinned requests the same pixel size at every scale, so the scale
332
// cancels out of the size calculation).
333
//
334
// Buffers still held by the compositor are only stripped of their front/back buffer
335
// role and are destroyed once released. Destroying a wl_buffer before it has been
336
// released will make some compositors display nothing until the next commit, causing
337
// flickering.
338
const
qreal
scale
=
waylandWindow
()->
scale
();
339
for
(
auto
i
=
mBuffers
.
size
() - 1;
i
>= 0; --
i
) {
340
QWaylandShmBuffer
*
buffer
=
mBuffers
[
i
];
341
if
(
buffer
->
size
() !=
size
342
|| !
qFuzzyCompare
(
buffer
->
image
()->
devicePixelRatio
(),
scale
)) {
343
if
(
mBackBuffer
==
buffer
)
344
mBackBuffer
=
nullptr
;
345
if
(
mFrontBuffer
==
buffer
)
346
mFrontBuffer
=
nullptr
;
347
if
(
buffer
->
busy
())
348
continue
;
349
mBuffers
.
removeAt
(
i
);
350
delete
buffer
;
351
}
352
}
353
354
QWaylandShmBuffer
*
buffer
=
nullptr
;
355
for
(
QWaylandShmBuffer
*
candidate
:
std
::
as_const
(
mBuffers
)) {
356
if
(
candidate
->
busy
())
357
continue
;
358
359
if
(!
buffer
||
candidate
->
age
() <
buffer
->
age
())
360
buffer
=
candidate
;
361
}
362
363
if
(
buffer
)
364
return
buffer
;
365
366
if
(
mBuffers
.
size
() <
MAX_BUFFERS
) {
367
QImage
::
Format
format
=
QImage
::
Format_ARGB32_Premultiplied
;
368
if
(!
waylandWindow
()->
format
().
hasAlpha
())
369
format
=
QImage
::
Format_RGB32
;
370
QWaylandShmBuffer
*
b
=
new
QWaylandShmBuffer
(
mDisplay
,
size
,
format
,
waylandWindow
()->
scale
(),
mEventQueue
);
371
bufferWasRecreated
=
true
;
372
mBuffers
.
push_front
(
b
);
373
return
b
;
374
}
375
return
nullptr
;
376
}
377
378
bool
QWaylandShmBackingStore
::
recreateBackBufferIfNeeded
()
379
{
380
wl_display_dispatch_queue_pending
(
mDisplay
->
wl_display
(),
mEventQueue
);
381
382
bool
bufferWasRecreated
=
false
;
383
qreal
scale
=
waylandWindow
()->
scale
();
384
const
QSize
devicePixelSize
=
mRequestedSize
*
scale
;
385
386
// We look for a free buffer to draw into. If the buffer is not the last buffer we used,
387
// that is mBackBuffer, and the size is the same we copy the damaged content into the new
388
// buffer so that QPainter is happy to find the stuff it had drawn before. If the new
389
// buffer has a different size it needs to be redrawn completely anyway, and if the buffer
390
// is the same the stuff is there already.
391
// You can exercise the different codepaths with weston, switching between the gl and the
392
// pixman renderer. With the gl renderer release events are sent early so we can effectively
393
// run single buffered, while with the pixman renderer we have to use two.
394
QWaylandShmBuffer
*
buffer
=
getBuffer
(
devicePixelSize
,
bufferWasRecreated
);
395
while
(!
buffer
) {
396
struct
::
wl_display
*
display
=
mDisplay
->
wl_display
();
397
if
(
wl_display_dispatch_queue
(
display
,
mEventQueue
) < 0) {
398
int
ecode
=
wl_display_get_error
(
display
);
399
if
((
ecode
==
EPIPE
||
ecode
==
ECONNRESET
))
400
qWarning
(
"The Wayland connection broke during blocking read event. Did the Wayland compositor die?"
);
401
else
402
qWarning
(
"The Wayland connection experienced a fatal error during blocking read event: %s"
,
strerror
(
ecode
));
403
_exit
(-1);
404
}
405
buffer
=
getBuffer
(
devicePixelSize
,
bufferWasRecreated
);
406
}
407
408
mBackBuffer
=
buffer
;
409
410
for
(
QWaylandShmBuffer
*
buffer
:
std
::
as_const
(
mBuffers
)) {
411
if
(
mBackBuffer
==
buffer
) {
412
buffer
->
setAge
(0);
413
}
else
{
414
buffer
->
setAge
(
buffer
->
age
() + 1);
415
}
416
}
417
418
return
bufferWasRecreated
;
419
}
420
421
void
QWaylandShmBackingStore
::
finalizeBackBuffer
()
422
{
423
Q_ASSERT
(
mBackBuffer
);
424
425
const
QRegion
clipRegion
=
mBackBuffer
->
dirtyRegion
();
426
if
(
clipRegion
.
isEmpty
())
427
return
;
428
429
if
(
Q_UNLIKELY
(!
mFrontBuffer
||
mFrontBuffer
==
mBackBuffer
))
430
return
;
431
432
const
QImage
*
sourceImage
=
mFrontBuffer
->
image
();
433
QImage
*
targetImage
=
mBackBuffer
->
image
();
434
435
QPainter
painter
(
targetImage
);
436
painter
.
setCompositionMode
(
QPainter
::
CompositionMode_Source
);
437
const
qreal
targetDevicePixelRatio
=
painter
.
device
()->
devicePixelRatio
();
438
const
auto
clipRects
=
clipRegion
.
rects
();
439
for
(
const
QRect
&
clipRect
:
clipRects
) {
// Iterate clip rects, because complicated clip region causes higher CPU usage
440
if
(
clipRects
.
size
() > 1)
441
painter
.
save
();
442
painter
.
setClipRect
(
clipRect
);
443
painter
.
scale
(
qreal
(1) /
targetDevicePixelRatio
,
qreal
(1) /
targetDevicePixelRatio
);
444
painter
.
drawImage
(
QRectF
(
QPointF
(),
targetImage
->
size
()), *
sourceImage
,
sourceImage
->
rect
());
445
if
(
clipRects
.
size
() > 1)
446
painter
.
restore
();
447
}
448
449
mBackBuffer
->
dirtyRegion
() =
QRegion
();
450
}
451
452
QImage
*
QWaylandShmBackingStore
::
contentSurface
()
const
453
{
454
return
mBackBuffer
->
image
();
455
}
456
457
QWaylandWindow
*
QWaylandShmBackingStore
::
waylandWindow
()
const
458
{
459
return
static_cast
<
QWaylandWindow
*>(
window
()->
handle
());
460
}
461
462
#
if
QT_CONFIG
(
opengl
)
463
QImage
QWaylandShmBackingStore
::
toImage
()
const
464
{
465
// Invoked from QPlatformBackingStore::composeAndFlush() that is called
466
// instead of flush() for widgets that have renderToTexture children
467
// (QOpenGLWidget, QQuickWidget).
468
469
const_cast
<
QWaylandShmBackingStore
*>(
this
)->
finalizeBackBuffer
();
470
471
return
*
contentSurface
();
472
}
473
#
endif
// opengl
474
475
}
476
477
QT_END_NAMESPACE
QtWaylandClient
Definition
qwaylandclientextension.h:16
QtWaylandClient::alignTo
static int alignTo(int input, int alignment)
Definition
qwaylandshmbackingstore.cpp:53
MAP_FAILED
#define MAP_FAILED
Definition
qresource.cpp:1189
qt_scrollRectInImage
QT_BEGIN_NAMESPACE void qt_scrollRectInImage(QImage &, const QRect &, const QPoint &)
Definition
qbackingstore.cpp:305
qtbase
src
plugins
platforms
wayland
qwaylandshmbackingstore.cpp
Generated on
for Qt by
1.18.0