Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qxcbbackingstore.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
6
8#include "qxcbscreen.h"
9#include "qxcbwindow.h"
10
11#include <xcb/shm.h>
12#include <xcb/xcb_image.h>
13#include <xcb/render.h>
14#include <xcb/xcb_renderutil.h>
15
16#include <sys/ipc.h>
17#include <sys/shm.h>
18#include <sys/mman.h>
19
20#include <stdio.h>
21#include <errno.h>
22#include <unistd.h>
23
24#include <qdebug.h>
25#include <qpainter.h>
26#include <qscreen.h>
27#include <QtGui/private/qhighdpiscaling_p.h>
28#include <qpa/qplatformgraphicsbuffer.h>
29#include <private/qimage_p.h>
30#include <qendian.h>
31
32#include <algorithm>
33
34#if (XCB_SHM_MAJOR_VERSION == 1 && XCB_SHM_MINOR_VERSION >= 2) || XCB_SHM_MAJOR_VERSION > 1
35#define XCB_USE_SHM_FD
36#endif
37
38QT_BEGIN_NAMESPACE
39
40class QXcbBackingStore;
41
43{
44public:
45 QXcbBackingStoreImage(QXcbBackingStore *backingStore, const QSize &size);
46 QXcbBackingStoreImage(QXcbBackingStore *backingStore, const QSize &size, uint depth, QImage::Format format);
47 ~QXcbBackingStoreImage() { destroy(true); }
48
49 void resize(const QSize &size);
50
51 void flushScrolledRegion(bool clientSideScroll);
52
53 bool scroll(const QRegion &area, int dx, int dy);
54
55 QImage *image() { return &m_qimage; }
56 QPlatformGraphicsBuffer *graphicsBuffer() { return m_graphics_buffer; }
57
58 QSize size() const { return m_qimage.size(); }
59
60 bool hasAlpha() const { return m_hasAlpha; }
61 bool hasShm() const { return m_shm_info.shmaddr != nullptr; }
62
63 void put(xcb_drawable_t dst, const QRegion &region, const QPoint &offset);
64 void preparePaint(const QRegion &region);
65
66 static bool createSystemVShmSegment(xcb_connection_t *c, size_t segmentSize = 1,
67 xcb_shm_segment_info_t *shm_info = nullptr);
68
69private:
70 void init(const QSize &size, uint depth, QImage::Format format);
71
72 void createShmSegment(size_t segmentSize);
73 void destroyShmSegment();
74 void destroy(bool destroyShm);
75
76 void ensureGC(xcb_drawable_t dst);
77 void shmPutImage(xcb_drawable_t drawable, const QRegion &region, const QPoint &offset = QPoint());
78 void flushPixmap(const QRegion &region, bool fullRegion = false);
79 void setClip(const QRegion &region);
80
81 xcb_shm_segment_info_t m_shm_info;
82 size_t m_segmentSize = 0;
83 QXcbBackingStore *m_backingStore = nullptr;
84
85 xcb_image_t *m_xcb_image = nullptr;
86
87 QImage m_qimage;
88 QPlatformGraphicsBuffer *m_graphics_buffer = nullptr;
89
90 xcb_gcontext_t m_gc = 0;
91 xcb_drawable_t m_gc_drawable = 0;
92
93 // When using shared memory these variables are used only for server-side scrolling.
94 // When not using shared memory, we maintain a server-side pixmap with the backing
95 // store as well as repainted content not yet flushed to the pixmap. We only flush
96 // the regions we need and only when these are marked dirty. This way we can just
97 // do a server-side copy on expose instead of sending the pixels every time
98 xcb_pixmap_t m_xcb_pixmap = 0;
99 QRegion m_pendingFlush;
100
101 // This is the scrolled region which is stored in server-side pixmap
102 QRegion m_scrolledRegion;
103
104 // When using shared memory this is the region currently shared with the server
105 QRegion m_dirtyShm;
106
107 // When not using shared memory this is a temporary buffer which is uploaded
108 // as a pixmap region to server
109 QByteArray m_flushBuffer;
110
111 bool m_hasAlpha = false;
112 bool m_clientSideScroll = false;
113
114 const xcb_format_t *m_xcb_format = nullptr;
115 QImage::Format m_qimage_format = QImage::Format_Invalid;
116};
117
119{
120public:
125
126 bool doLock(AccessTypes access, const QRect &rect) override
127 {
128 Q_UNUSED(rect);
129 if (access & ~(QPlatformGraphicsBuffer::SWReadAccess | QPlatformGraphicsBuffer::SWWriteAccess))
130 return false;
131
132 m_access_lock |= access;
133 return true;
134 }
135 void doUnlock() override { m_access_lock = None; }
136
137 const uchar *data() const override { return m_image->bits(); }
138 uchar *data() override { return m_image->bits(); }
139 int bytesPerLine() const override { return m_image->bytesPerLine(); }
140
141 Origin origin() const override { return QPlatformGraphicsBuffer::OriginTopLeft; }
142
143private:
144 AccessTypes m_access_lock = QPlatformGraphicsBuffer::None;
145 QImage *m_image = nullptr;
146};
147
148static inline size_t imageDataSize(const xcb_image_t *image)
149{
150 return static_cast<size_t>(image->stride) * image->height;
151}
152
154 : QXcbObject(backingStore->connection())
155 , m_backingStore(backingStore)
156{
157 auto window = static_cast<QXcbWindow *>(m_backingStore->window()->handle());
158 init(size, window->depth(), window->imageFormat());
159}
160
162 uint depth, QImage::Format format)
163 : QXcbObject(backingStore->connection())
164 , m_backingStore(backingStore)
165{
166 init(size, depth, format);
167}
168
169void QXcbBackingStoreImage::init(const QSize &size, uint depth, QImage::Format format)
170{
171 m_xcb_format = connection()->formatForDepth(depth);
172 Q_ASSERT(m_xcb_format);
173
174 m_qimage_format = format;
175 m_hasAlpha = QImage::toPixelFormat(m_qimage_format).alphaUsage() == QPixelFormat::UsesAlpha;
176 if (!m_hasAlpha)
177 m_qimage_format = qt_maybeDataCompatibleAlphaVersion(m_qimage_format);
178
179 memset(&m_shm_info, 0, sizeof m_shm_info);
180
181 resize(size);
182}
183
184void QXcbBackingStoreImage::resize(const QSize &size)
185{
186 destroy(false);
187
188 auto byteOrder = QSysInfo::ByteOrder == QSysInfo::BigEndian ? XCB_IMAGE_ORDER_MSB_FIRST
189 : XCB_IMAGE_ORDER_LSB_FIRST;
190 m_xcb_image = xcb_image_create(size.width(), size.height(),
191 XCB_IMAGE_FORMAT_Z_PIXMAP,
192 m_xcb_format->scanline_pad,
193 m_xcb_format->depth,
194 m_xcb_format->bits_per_pixel,
195 0, byteOrder,
196 XCB_IMAGE_ORDER_MSB_FIRST,
197 nullptr, ~0, nullptr);
198
199 const size_t segmentSize = imageDataSize(m_xcb_image);
200
201 if (connection()->hasShm()) {
202 if (segmentSize == 0) {
203 if (m_segmentSize > 0) {
204 destroyShmSegment();
205 qCDebug(lcQpaXcb) << "[" << m_backingStore->window()
206 << "] destroyed SHM segment due to resize to" << size;
207 }
208 } else {
209 // Destroy shared memory segment if it is double (or more) of what we actually
210 // need with new window size. Or if the new size is bigger than what we currently
211 // have allocated.
212 if (m_shm_info.shmaddr && (m_segmentSize < segmentSize || m_segmentSize / 2 >= segmentSize))
213 destroyShmSegment();
214 if (!m_shm_info.shmaddr) {
215 qCDebug(lcQpaXcb) << "[" << m_backingStore->window()
216 << "] creating shared memory" << segmentSize << "bytes for"
217 << size << "depth" << m_xcb_format->depth << "bits"
218 << m_xcb_format->bits_per_pixel;
219 createShmSegment(segmentSize);
220 }
221 }
222 }
223
224 if (segmentSize == 0)
225 return;
226
227 m_xcb_image->data = m_shm_info.shmaddr ? m_shm_info.shmaddr : (uint8_t *)malloc(segmentSize);
228 m_qimage = QImage(static_cast<uchar *>(m_xcb_image->data), m_xcb_image->width,
229 m_xcb_image->height, m_xcb_image->stride, m_qimage_format);
230 m_graphics_buffer = new QXcbGraphicsBuffer(&m_qimage);
231
232 m_xcb_pixmap = xcb_generate_id(xcb_connection());
233 auto xcbScreen = static_cast<QXcbScreen *>(m_backingStore->window()->screen()->handle());
234 xcb_create_pixmap(xcb_connection(),
235 m_xcb_image->depth,
236 m_xcb_pixmap,
237 xcbScreen->root(),
238 m_xcb_image->width, m_xcb_image->height);
239}
240
241void QXcbBackingStoreImage::destroy(bool destroyShm)
242{
243 if (m_xcb_image) {
244 if (m_xcb_image->data) {
245 if (m_shm_info.shmaddr) {
246 if (destroyShm)
247 destroyShmSegment();
248 } else {
249 free(m_xcb_image->data);
250 }
251 }
252 xcb_image_destroy(m_xcb_image);
253 }
254
255 if (m_gc) {
256 xcb_free_gc(xcb_connection(), m_gc);
257 m_gc = 0;
258 }
259 m_gc_drawable = 0;
260
261 delete m_graphics_buffer;
262 m_graphics_buffer = nullptr;
263
264 if (m_xcb_pixmap) {
265 xcb_free_pixmap(xcb_connection(), m_xcb_pixmap);
266 m_xcb_pixmap = 0;
267 }
268
269 m_qimage = QImage();
270}
271
272void QXcbBackingStoreImage::flushScrolledRegion(bool clientSideScroll)
273{
274 if (m_clientSideScroll == clientSideScroll)
275 return;
276
277 m_clientSideScroll = clientSideScroll;
278
279 if (m_scrolledRegion.isNull())
280 return;
281
282 if (hasShm() && m_dirtyShm.intersects(m_scrolledRegion)) {
283 connection()->sync();
284 m_dirtyShm = QRegion();
285 }
286
287 if (m_clientSideScroll) {
288 // Copy scrolled image region from server-side pixmap to client-side memory
289 for (const QRect &rect : m_scrolledRegion) {
290 const int w = rect.width();
291 const int h = rect.height();
292
293 auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_image,
294 xcb_connection(),
295 m_xcb_image->format,
296 m_xcb_pixmap,
297 rect.x(), rect.y(),
298 w, h,
299 ~0u);
300
301 if (reply && reply->depth == m_xcb_image->depth) {
302 const QImage img(xcb_get_image_data(reply.get()), w, h, m_qimage.format());
303
304 QPainter p(&m_qimage);
305 p.setCompositionMode(QPainter::CompositionMode_Source);
306 p.drawImage(rect.topLeft(), img);
307 }
308 }
309 m_scrolledRegion = QRegion();
310 } else {
311 // Copy scrolled image region from client-side memory to server-side pixmap
312 ensureGC(m_xcb_pixmap);
313 if (hasShm())
314 shmPutImage(m_xcb_pixmap, m_scrolledRegion);
315 else
316 flushPixmap(m_scrolledRegion, true);
317 }
318}
319
320void QXcbBackingStoreImage::createShmSegment(size_t segmentSize)
321{
322 Q_ASSERT(connection()->hasShm());
323 Q_ASSERT(m_segmentSize == 0);
324
325#ifdef XCB_USE_SHM_FD
326 if (connection()->hasShmFd()) {
327 if (Q_UNLIKELY(segmentSize > std::numeric_limits<uint32_t>::max())) {
328 qCWarning(lcQpaXcb, "xcb_shm_create_segment() can't be called for size %zu, maximum"
329 "allowed size is %u", segmentSize, std::numeric_limits<uint32_t>::max());
330 return;
331 }
332
333 const auto seg = xcb_generate_id(xcb_connection());
334 auto reply = Q_XCB_REPLY(xcb_shm_create_segment,
335 xcb_connection(), seg, segmentSize, false);
336 if (!reply) {
337 qCWarning(lcQpaXcb, "xcb_shm_create_segment() failed for size %zu", segmentSize);
338 return;
339 }
340
341 int *fds = xcb_shm_create_segment_reply_fds(xcb_connection(), reply.get());
342 if (reply->nfd != 1) {
343 for (int i = 0; i < reply->nfd; i++)
344 close(fds[i]);
345
346 qCWarning(lcQpaXcb, "failed to get file descriptor for shm segment of size %zu", segmentSize);
347 return;
348 }
349
350 void *addr = mmap(nullptr, segmentSize, PROT_READ|PROT_WRITE, MAP_SHARED, fds[0], 0);
351 if (addr == MAP_FAILED) {
352 qCWarning(lcQpaXcb, "failed to mmap segment from X server (%d: %s) for size %zu",
353 errno, strerror(errno), segmentSize);
354 close(fds[0]);
355 xcb_shm_detach(xcb_connection(), seg);
356 return;
357 }
358
359 close(fds[0]);
360 m_shm_info.shmseg = seg;
361 m_shm_info.shmaddr = static_cast<quint8 *>(addr);
362 m_segmentSize = segmentSize;
363 } else
364#endif
365 {
366 if (createSystemVShmSegment(xcb_connection(), segmentSize, &m_shm_info))
367 m_segmentSize = segmentSize;
368 }
369}
370
371bool QXcbBackingStoreImage::createSystemVShmSegment(xcb_connection_t *c, size_t segmentSize,
372 xcb_shm_segment_info_t *shmInfo)
373{
374 const int id = shmget(IPC_PRIVATE, segmentSize, IPC_CREAT | 0600);
375 if (id == -1) {
376 qCWarning(lcQpaXcb, "shmget() failed (%d: %s) for size %zu", errno, strerror(errno), segmentSize);
377 return false;
378 }
379
380 void *addr = shmat(id, nullptr, 0);
381 if (addr == (void *)-1) {
382 qCWarning(lcQpaXcb, "shmat() failed (%d: %s) for id %d", errno, strerror(errno), id);
383 return false;
384 }
385
386 if (shmctl(id, IPC_RMID, nullptr) == -1)
387 qCWarning(lcQpaXcb, "Error while marking the shared memory segment to be destroyed");
388
389 const auto seg = xcb_generate_id(c);
390 auto cookie = xcb_shm_attach_checked(c, seg, id, false);
391 auto *error = xcb_request_check(c, cookie);
392 if (error) {
393 qCWarning(lcQpaXcb(), "xcb_shm_attach() failed");
394 free(error);
395 if (shmdt(addr) == -1)
396 qCWarning(lcQpaXcb, "shmdt() failed (%d: %s) for %p", errno, strerror(errno), addr);
397 return false;
398 } else if (!shmInfo) { // this was a test run, free the allocated test segment
399 xcb_shm_detach(c, seg);
400 auto shmaddr = static_cast<quint8 *>(addr);
401 if (shmdt(shmaddr) == -1)
402 qCWarning(lcQpaXcb, "shmdt() failed (%d: %s) for %p", errno, strerror(errno), shmaddr);
403 }
404 if (shmInfo) {
405 shmInfo->shmseg = seg;
406 shmInfo->shmid = id; // unused
407 shmInfo->shmaddr = static_cast<quint8 *>(addr);
408 }
409 return true;
410}
411
412void QXcbBackingStoreImage::destroyShmSegment()
413{
414 auto cookie = xcb_shm_detach_checked(xcb_connection(), m_shm_info.shmseg);
415 xcb_generic_error_t *error = xcb_request_check(xcb_connection(), cookie);
416 if (error)
417 connection()->printXcbError("xcb_shm_detach() failed with error", error);
418 m_shm_info.shmseg = 0;
419
420#ifdef XCB_USE_SHM_FD
421 if (connection()->hasShmFd()) {
422 if (munmap(m_shm_info.shmaddr, m_segmentSize) == -1) {
423 qCWarning(lcQpaXcb, "munmap() failed (%d: %s) for %p with size %zu",
424 errno, strerror(errno), m_shm_info.shmaddr, m_segmentSize);
425 }
426 } else
427#endif
428 {
429 if (shmdt(m_shm_info.shmaddr) == -1) {
430 qCWarning(lcQpaXcb, "shmdt() failed (%d: %s) for %p",
431 errno, strerror(errno), m_shm_info.shmaddr);
432 }
433 m_shm_info.shmid = 0; // unused
434 }
435 m_shm_info.shmaddr = nullptr;
436
437 m_segmentSize = 0;
438}
439
440extern void qt_scrollRectInImage(QImage &img, const QRect &rect, const QPoint &offset);
441
442bool QXcbBackingStoreImage::scroll(const QRegion &area, int dx, int dy)
443{
444 const QRect bounds(QPoint(), size());
445 const QRegion scrollArea(area & bounds);
446 const QPoint delta(dx, dy);
447 const QRegion destinationRegion = scrollArea.translated(delta).intersected(bounds);
448
449 if (m_clientSideScroll) {
450 if (m_qimage.isNull())
451 return false;
452
453 if (hasShm())
454 preparePaint(destinationRegion);
455
456 const QRect rect = scrollArea.boundingRect();
457 qt_scrollRectInImage(m_qimage, rect, delta);
458 } else {
459 ensureGC(m_xcb_pixmap);
460
461 if (hasShm()) {
462 QRegion partialFlushRegion = m_pendingFlush.intersected(scrollArea);
463 shmPutImage(m_xcb_pixmap, partialFlushRegion);
464 m_pendingFlush -= partialFlushRegion;
465 } else {
466 flushPixmap(scrollArea);
467 }
468
469 for (const QRect &src : scrollArea) {
470 const QRect dst = src.translated(delta).intersected(bounds);
471 xcb_copy_area(xcb_connection(),
472 m_xcb_pixmap,
473 m_xcb_pixmap,
474 m_gc,
475 src.x(), src.y(),
476 dst.x(), dst.y(),
477 dst.width(), dst.height());
478 }
479
480 if (hasShm())
481 m_pendingFlush -= destinationRegion;
482 }
483
484 m_scrolledRegion |= destinationRegion;
485
486 return true;
487}
488
489void QXcbBackingStoreImage::ensureGC(xcb_drawable_t dst)
490{
491 if (m_gc_drawable != dst) {
492 if (m_gc)
493 xcb_free_gc(xcb_connection(), m_gc);
494
495 static const uint32_t mask = XCB_GC_GRAPHICS_EXPOSURES;
496 static const uint32_t values[] = { 0 };
497
498 m_gc = xcb_generate_id(xcb_connection());
499 xcb_create_gc(xcb_connection(), m_gc, dst, mask, values);
500
501 m_gc_drawable = dst;
502 }
503}
504
505static inline void copy_unswapped(char *dst, int dstBytesPerLine, const QImage &img, const QRect &rect)
506{
507 const uchar *srcData = img.constBits();
508 const qsizetype srcBytesPerLine = img.bytesPerLine();
509
510 const int leftOffset = rect.left() * img.depth() >> 3;
511 const int bottom = rect.bottom() + 1;
512
513 for (int yy = rect.top(); yy < bottom; ++yy) {
514 const uchar *src = srcData + yy * srcBytesPerLine + leftOffset;
515 ::memmove(dst, src, dstBytesPerLine);
516 dst += dstBytesPerLine;
517 }
518}
519
520template <class Pixel>
521static inline void copy_swapped(char *dst, const int dstStride, const QImage &img, const QRect &rect)
522{
523 const uchar *srcData = img.constBits();
524 const qsizetype srcBytesPerLine = img.bytesPerLine();
525
526 const int left = rect.left();
527 const int width = rect.width();
528 const int bottom = rect.bottom() + 1;
529
530 for (int yy = rect.top(); yy < bottom; ++yy) {
531 Pixel *dstPixels = reinterpret_cast<Pixel *>(dst);
532 const Pixel *srcPixels = reinterpret_cast<const Pixel *>(srcData + yy * srcBytesPerLine) + left;
533
534 for (int i = 0; i < width; ++i)
535 dstPixels[i] = qbswap<Pixel>(*srcPixels++);
536
537 dst += dstStride;
538 }
539}
540
541static QImage native_sub_image(QByteArray *buffer, const int dstStride, const QImage &src, const QRect &rect, bool swap)
542{
543 if (!swap && src.rect() == rect && src.bytesPerLine() == dstStride)
544 return src;
545
546 buffer->resize(rect.height() * dstStride);
547
548 if (swap) {
549 switch (src.depth()) {
550 case 32:
551 copy_swapped<quint32>(buffer->data(), dstStride, src, rect);
552 break;
553 case 16:
554 copy_swapped<quint16>(buffer->data(), dstStride, src, rect);
555 break;
556 }
557 } else {
558 copy_unswapped(buffer->data(), dstStride, src, rect);
559 }
560
561 return QImage(reinterpret_cast<const uchar *>(buffer->constData()), rect.width(), rect.height(), dstStride, src.format());
562}
563
564static inline quint32 round_up_scanline(quint32 base, quint32 pad)
565{
566 return (base + pad - 1) & -pad;
567}
568
569void QXcbBackingStoreImage::shmPutImage(xcb_drawable_t drawable, const QRegion &region, const QPoint &offset)
570{
571 for (const QRect &rect : region) {
572 const QPoint source = rect.translated(offset).topLeft();
573 xcb_shm_put_image(xcb_connection(),
574 drawable,
575 m_gc,
576 m_xcb_image->width,
577 m_xcb_image->height,
578 source.x(), source.y(),
579 rect.width(), rect.height(),
580 rect.x(), rect.y(),
581 m_xcb_image->depth,
582 m_xcb_image->format,
583 0, // send event?
584 m_shm_info.shmseg,
585 m_xcb_image->data - m_shm_info.shmaddr);
586 }
587 m_dirtyShm |= region.translated(offset);
588}
589
590void QXcbBackingStoreImage::flushPixmap(const QRegion &region, bool fullRegion)
591{
592 if (!fullRegion) {
593 auto actualRegion = m_pendingFlush.intersected(region);
594 m_pendingFlush -= region;
595 flushPixmap(actualRegion, true);
596 return;
597 }
598
599 xcb_image_t xcb_subimage;
600 memset(&xcb_subimage, 0, sizeof(xcb_image_t));
601
602 xcb_subimage.format = m_xcb_image->format;
603 xcb_subimage.scanline_pad = m_xcb_image->scanline_pad;
604 xcb_subimage.depth = m_xcb_image->depth;
605 xcb_subimage.bpp = m_xcb_image->bpp;
606 xcb_subimage.unit = m_xcb_image->unit;
607 xcb_subimage.plane_mask = m_xcb_image->plane_mask;
608 xcb_subimage.byte_order = (xcb_image_order_t) connection()->setup()->image_byte_order;
609 xcb_subimage.bit_order = m_xcb_image->bit_order;
610
611 const bool needsByteSwap = xcb_subimage.byte_order != m_xcb_image->byte_order;
612 // Ensure that we don't send more than maxPutImageRequestDataBytes per request.
613 const auto maxPutImageRequestDataBytes = connection()->maxRequestDataBytes(sizeof(xcb_put_image_request_t));
614
615 for (const QRect &rect : region) {
616 const quint32 stride = round_up_scanline(rect.width() * m_qimage.depth(), xcb_subimage.scanline_pad) >> 3;
617 const int rows_per_put = maxPutImageRequestDataBytes / stride;
618
619 // This assert could trigger if a single row has more pixels than fit in
620 // a single PutImage request. In the absence of the BIG-REQUESTS extension
621 // the theoretical maximum lengths of maxPutImageRequestDataBytes can be
622 // roughly 256kB.
623 Q_ASSERT(rows_per_put > 0);
624
625 // If we upload the whole image in a single chunk, the result might be
626 // larger than the server's maximum request size and stuff breaks.
627 // To work around that, we upload the image in chunks where each chunk
628 // is small enough for a single request.
629 const int x = rect.x();
630 int y = rect.y();
631 const int width = rect.width();
632 int height = rect.height();
633
634 while (height > 0) {
635 const int rows = std::min(height, rows_per_put);
636 const QRect subRect(x, y, width, rows);
637 const QImage subImage = native_sub_image(&m_flushBuffer, stride, m_qimage, subRect, needsByteSwap);
638
639 Q_ASSERT(static_cast<size_t>(subImage.sizeInBytes()) <= maxPutImageRequestDataBytes);
640
641 xcb_subimage.width = width;
642 xcb_subimage.height = rows;
643 xcb_subimage.data = const_cast<uint8_t *>(subImage.constBits());
644 xcb_image_annotate(&xcb_subimage);
645
646 xcb_image_put(xcb_connection(),
647 m_xcb_pixmap,
648 m_gc,
649 &xcb_subimage,
650 x,
651 y,
652 0);
653
654 y += rows;
655 height -= rows;
656 }
657 }
658}
659
660void QXcbBackingStoreImage::setClip(const QRegion &region)
661{
662 if (region.isEmpty()) {
663 static const uint32_t mask = XCB_GC_CLIP_MASK;
664 static const uint32_t values[] = { XCB_NONE };
665 xcb_change_gc(xcb_connection(), m_gc, mask, values);
666 } else {
667 const auto xcb_rects = qRegionToXcbRectangleList(region);
668 xcb_set_clip_rectangles(xcb_connection(),
669 XCB_CLIP_ORDERING_YX_BANDED,
670 m_gc,
671 0, 0,
672 xcb_rects.size(), xcb_rects.constData());
673 }
674}
675
676void QXcbBackingStoreImage::put(xcb_drawable_t dst, const QRegion &region, const QPoint &offset)
677{
678 Q_ASSERT(!m_clientSideScroll);
679
680 ensureGC(dst);
681
682 if (hasShm()) {
683 setClip(region); // Clip in window local coordinates
684
685 // Copy scrolled area on server-side from pixmap to window
686 const QRegion scrolledRegion = m_scrolledRegion.translated(-offset);
687 for (const QRect &rect : scrolledRegion) {
688 const QPoint source = rect.translated(offset).topLeft();
689 xcb_copy_area(xcb_connection(),
690 m_xcb_pixmap,
691 dst,
692 m_gc,
693 source.x(), source.y(),
694 rect.x(), rect.y(),
695 rect.width(), rect.height());
696 }
697
698 // Copy non-scrolled image from client-side memory to server-side window
699 const QRegion notScrolledArea = region - scrolledRegion;
700 shmPutImage(dst, notScrolledArea, offset);
701 } else {
702 const QRect bounds = region.boundingRect();
703 const QPoint target = bounds.topLeft();
704 const QRect source = bounds.translated(offset);
705
706 // First clip in backingstore-local coordinates, and upload
707 // the changed parts of the backingstore to the server.
708 setClip(source);
709 flushPixmap(source);
710
711 // Then clip in window local coordinates, and copy the updated
712 // parts of the backingstore image server-side to the window.
713 setClip(region);
714 xcb_copy_area(xcb_connection(),
715 m_xcb_pixmap,
716 dst,
717 m_gc,
718 source.x(), source.y(),
719 target.x(), target.y(),
720 source.width(), source.height());
721 }
722
723 setClip(QRegion());
724}
725
726void QXcbBackingStoreImage::preparePaint(const QRegion &region)
727{
728 if (hasShm()) {
729 // to prevent X from reading from the image region while we're writing to it
730 if (m_dirtyShm.intersects(region)) {
731 connection()->sync();
732 m_dirtyShm = QRegion();
733 }
734 }
735 m_scrolledRegion -= region;
736 m_pendingFlush |= region;
737}
738
739bool QXcbBackingStore::createSystemVShmSegment(xcb_connection_t *c, size_t segmentSize, void *shmInfo)
740{
741 auto info = reinterpret_cast<xcb_shm_segment_info_t *>(shmInfo);
742 return QXcbBackingStoreImage::createSystemVShmSegment(c, segmentSize, info);
743}
744
745QXcbBackingStore::QXcbBackingStore(QWindow *window)
746 : QPlatformBackingStore(window)
747{
748 QXcbScreen *screen = static_cast<QXcbScreen *>(window->screen()->handle());
749 setConnection(screen->connection());
750}
751
753{
754 delete m_image;
755}
756
758{
759 if (!m_image)
760 return nullptr;
761 return m_rgbImage.isNull() ? m_image->image() : &m_rgbImage;
762}
763
764void QXcbBackingStore::beginPaint(const QRegion &region)
765{
766 if (!m_image)
767 return;
768
769 m_paintRegions.push(region);
770 m_image->preparePaint(region);
771
773 QPainter p(paintDevice());
774 p.setCompositionMode(QPainter::CompositionMode_Source);
775 const QColor blank = Qt::transparent;
776 for (const QRect &rect : region)
777 p.fillRect(rect, blank);
778 }
779}
780
782{
783 if (Q_UNLIKELY(m_paintRegions.isEmpty())) {
784 qCWarning(lcQpaXcb, "%s: paint regions empty!", Q_FUNC_INFO);
785 return;
786 }
787
788 const QRegion region = m_paintRegions.pop();
789 m_image->preparePaint(region);
790
791 QXcbWindow *platformWindow = static_cast<QXcbWindow *>(window()->handle());
792 if (!platformWindow || !platformWindow->imageNeedsRgbSwap())
793 return;
794
795 // Slow path: the paint device was m_rgbImage. Now copy with swapping red
796 // and blue into m_image.
797 auto it = region.begin();
798 const auto end = region.end();
799 if (it == end)
800 return;
801 QPainter p(m_image->image());
802 while (it != end) {
803 const QRect rect = *(it++);
804 p.drawImage(rect.topLeft(), m_rgbImage.copy(rect).rgbSwapped());
805 }
806}
807
809{
810 // If the backingstore is rgbSwapped, return the internal image type here.
811 if (!m_rgbImage.isNull())
812 return m_rgbImage;
813
814 if (!m_image || !m_image->image())
815 return QImage();
816
818
819 QImage image = *m_image->image();
820
821 // Return an image that does not share QImageData with the original image,
822 // even if they both point to the same data of the m_xcb_image, otherwise
823 // painting to m_qimage would detach it from the m_xcb_image data.
824 return QImage(image.constBits(), image.width(), image.height(), image.format());
825}
826
828{
829 return m_image ? m_image->graphicsBuffer() : nullptr;
830}
831
832void QXcbBackingStore::flush(QWindow *window, const QRegion &region, const QPoint &offset)
833{
834 if (!m_image || m_image->size().isEmpty())
835 return;
836
838
839 QSize imageSize = m_image->size();
840
841 QRegion clipped = region;
842 clipped &= QRect(QPoint(), QHighDpi::toNativePixels(window->size(), window));
843 clipped &= QRect(0, 0, imageSize.width(), imageSize.height()).translated(-offset);
844
845 QRect bounds = clipped.boundingRect();
846
847 if (bounds.isNull())
848 return;
849
850 QXcbWindow *platformWindow = static_cast<QXcbWindow *>(window->handle());
851 if (!platformWindow) {
852 qCWarning(lcQpaXcb, "%s QWindow has no platform window, see QTBUG-32681", Q_FUNC_INFO);
853 return;
854 }
855
856 render(platformWindow->xcb_window(), clipped, offset);
857
858 if (platformWindow->needsSync())
859 platformWindow->updateSyncRequestCounter();
860 else
861 xcb_flush(xcb_connection());
862}
863
864void QXcbBackingStore::render(xcb_window_t window, const QRegion &region, const QPoint &offset)
865{
866 m_image->put(window, region, offset);
867}
868
869QPlatformBackingStore::FlushResult QXcbBackingStore::rhiFlush(QWindow *window,
870 qreal sourceDevicePixelRatio,
871 const QRegion &region,
872 const QPoint &offset,
873 QPlatformTextureList *textures,
874 bool translucentBackground,
875 qreal sourceTransformFactor)
876{
877 if (!m_image || m_image->size().isEmpty())
878 return FlushFailed;
879
881
882 auto result = QPlatformBackingStore::rhiFlush(window, sourceDevicePixelRatio, region, offset,
883 textures, translucentBackground, sourceTransformFactor);
884 if (result != FlushSuccess)
885 return result;
886 QXcbWindow *platformWindow = static_cast<QXcbWindow *>(window->handle());
887 if (platformWindow->needsSync()) {
888 platformWindow->updateSyncRequestCounter();
889 } else {
890 xcb_flush(xcb_connection());
891 }
892
893 return FlushSuccess;
894}
895
896void QXcbBackingStore::resize(const QSize &size, const QRegion &)
897{
898 if (m_image && size == m_image->size())
899 return;
900
901 QPlatformWindow *pw = window()->handle();
902 if (!pw) {
903 window()->create();
904 pw = window()->handle();
905 }
906 QXcbWindow* win = static_cast<QXcbWindow *>(pw);
907
908 recreateImage(win, size);
909}
910
911void QXcbBackingStore::recreateImage(QXcbWindow *win, const QSize &size)
912{
913 if (m_image)
914 m_image->resize(size);
915 else
916 m_image = new QXcbBackingStoreImage(this, size);
917
918 // Slow path for bgr888 VNC: Create an additional image, paint into that and
919 // swap R and B while copying to m_image after each paint.
920 if (win->imageNeedsRgbSwap()) {
921 m_rgbImage = QImage(size, win->imageFormat());
922 }
923}
924
925bool QXcbBackingStore::scroll(const QRegion &area, int dx, int dy)
926{
927 if (m_image)
928 return m_image->scroll(area, dx, dy);
929
930 return false;
931}
932
933QXcbSystemTrayBackingStore::QXcbSystemTrayBackingStore(QWindow *window)
934 : QXcbBackingStore(window)
935{
936 // We need three different behaviors depending on whether the X11 visual
937 // for the system tray supports an alpha channel, i.e. is 32 bits, and
938 // whether XRender can be used:
939 // 1) if the visual has an alpha channel, then render the window's buffer
940 // directly to the X11 window as usual
941 // 2) else if XRender can be used, then render the window's buffer to Pixmap,
942 // then render Pixmap's contents to the cleared X11 window with
943 // xcb_render_composite()
944 // 3) else grab the X11 window's content and paint it first each time as a
945 // background before rendering the window's buffer to the X11 window
946
947 auto *platformWindow = static_cast<QXcbWindow *>(window->handle());
948 quint8 depth = connection()->primaryScreen()->depthOfVisual(platformWindow->visualId());
949
950 if (depth != 32) {
951 platformWindow->setParentRelativeBackPixmap();
952 initXRenderMode();
953 m_useGrabbedBackgound = !m_usingXRenderMode;
954 }
955}
956
958{
959 if (m_xrenderPicture) {
960 xcb_render_free_picture(xcb_connection(), m_xrenderPicture);
961 m_xrenderPicture = XCB_NONE;
962 }
963 if (m_xrenderPixmap) {
964 xcb_free_pixmap(xcb_connection(), m_xrenderPixmap);
965 m_xrenderPixmap = XCB_NONE;
966 }
967 if (m_windowPicture) {
968 xcb_render_free_picture(xcb_connection(), m_windowPicture);
969 m_windowPicture = XCB_NONE;
970 }
971}
972
973void QXcbSystemTrayBackingStore::beginPaint(const QRegion &region)
974{
975 QXcbBackingStore::beginPaint(region);
976
977 if (m_useGrabbedBackgound) {
978 QPainter p(paintDevice());
979 p.setCompositionMode(QPainter::CompositionMode_Source);
980 for (const QRect &rect: region)
981 p.drawPixmap(rect, m_grabbedBackground, rect);
982 }
983}
984
985void QXcbSystemTrayBackingStore::render(xcb_window_t window, const QRegion &region, const QPoint &offset)
986{
987 if (!m_usingXRenderMode) {
988 QXcbBackingStore::render(window, region, offset);
989 return;
990 }
991
992 m_image->put(m_xrenderPixmap, region, offset);
993 const QRect bounds = region.boundingRect();
994 const QPoint target = bounds.topLeft();
995 const QRect source = bounds.translated(offset);
996 xcb_clear_area(xcb_connection(), false, window,
997 target.x(), target.y(), source.width(), source.height());
998 xcb_render_composite(xcb_connection(), XCB_RENDER_PICT_OP_OVER,
999 m_xrenderPicture, 0, m_windowPicture,
1000 target.x(), target.y(), 0, 0, target.x(), target.y(),
1001 source.width(), source.height());
1002}
1003
1004void QXcbSystemTrayBackingStore::recreateImage(QXcbWindow *win, const QSize &size)
1005{
1006 if (!m_usingXRenderMode) {
1007 QXcbBackingStore::recreateImage(win, size);
1008
1009 if (m_useGrabbedBackgound) {
1010 xcb_clear_area(xcb_connection(), false, win->xcb_window(),
1011 0, 0, size.width(), size.height());
1012 m_grabbedBackground = win->xcbScreen()->grabWindow(win->winId(), 0, 0,
1013 size.width(), size.height());
1014 }
1015 return;
1016 }
1017
1018 if (m_xrenderPicture) {
1019 xcb_render_free_picture(xcb_connection(), m_xrenderPicture);
1020 m_xrenderPicture = XCB_NONE;
1021 }
1022 if (m_xrenderPixmap) {
1023 xcb_free_pixmap(xcb_connection(), m_xrenderPixmap);
1024 m_xrenderPixmap = XCB_NONE;
1025 }
1026
1027 QXcbScreen *screen = win->xcbScreen();
1028
1029 m_xrenderPixmap = xcb_generate_id(xcb_connection());
1030 xcb_create_pixmap(xcb_connection(), 32, m_xrenderPixmap, screen->root(), size.width(), size.height());
1031
1032 m_xrenderPicture = xcb_generate_id(xcb_connection());
1033 xcb_render_create_picture(xcb_connection(), m_xrenderPicture, m_xrenderPixmap, m_xrenderPictFormat, 0, nullptr);
1034
1035 // XRender expects premultiplied alpha
1036 if (m_image)
1037 m_image->resize(size);
1038 else
1039 m_image = new QXcbBackingStoreImage(this, size, 32, QImage::Format_ARGB32_Premultiplied);
1040}
1041
1042void QXcbSystemTrayBackingStore::initXRenderMode()
1043{
1044 if (!connection()->hasXRender())
1045 return;
1046
1047 xcb_connection_t *conn = xcb_connection();
1048 auto formatsReply = Q_XCB_REPLY(xcb_render_query_pict_formats, conn);
1049
1050 if (!formatsReply) {
1051 qWarning("QXcbSystemTrayBackingStore: xcb_render_query_pict_formats() failed");
1052 return;
1053 }
1054
1055 xcb_render_pictforminfo_t *fmt = xcb_render_util_find_standard_format(formatsReply.get(),
1056 XCB_PICT_STANDARD_ARGB_32);
1057 if (!fmt) {
1058 qWarning("QXcbSystemTrayBackingStore: Failed to find format PICT_STANDARD_ARGB_32");
1059 return;
1060 }
1061
1062 m_xrenderPictFormat = fmt->id;
1063
1064 auto *platformWindow = static_cast<QXcbWindow *>(window()->handle());
1065 xcb_render_pictvisual_t *vfmt = xcb_render_util_find_visual_format(formatsReply.get(), platformWindow->visualId());
1066
1067 if (!vfmt) {
1068 qWarning("QXcbSystemTrayBackingStore: Failed to find format for visual %x", platformWindow->visualId());
1069 return;
1070 }
1071
1072 m_windowPicture = xcb_generate_id(conn);
1073 xcb_void_cookie_t cookie =
1074 xcb_render_create_picture_checked(conn, m_windowPicture, platformWindow->xcb_window(), vfmt->format, 0, nullptr);
1075 xcb_generic_error_t *error = xcb_request_check(conn, cookie);
1076 if (error) {
1077 qWarning("QXcbSystemTrayBackingStore: Failed to create Picture with format %x for window %x, error code %d",
1078 vfmt->format, platformWindow->xcb_window(), error->error_code);
1079 free(error);
1080 return;
1081 }
1082
1083 m_usingXRenderMode = true;
1084}
1085
1086QT_END_NAMESPACE
QPainter(QPaintDevice *)
Constructs a painter that begins painting the paint device immediately.
QRect window() const
Returns the window rectangle.
void flushScrolledRegion(bool clientSideScroll)
QXcbBackingStoreImage(QXcbBackingStore *backingStore, const QSize &size)
void put(xcb_drawable_t dst, const QRegion &region, const QPoint &offset)
void resize(const QSize &size)
void preparePaint(const QRegion &region)
QXcbBackingStoreImage(QXcbBackingStore *backingStore, const QSize &size, uint depth, QImage::Format format)
QPlatformGraphicsBuffer * graphicsBuffer()
bool scroll(const QRegion &area, int dx, int dy)
static bool createSystemVShmSegment(xcb_connection_t *c, size_t segmentSize=1, xcb_shm_segment_info_t *shm_info=nullptr)
QXcbBackingStoreImage * m_image
QPlatformGraphicsBuffer * graphicsBuffer() const override
Accessor for a backingstores graphics buffer abstraction.
void resize(const QSize &size, const QRegion &staticContents) override
QPaintDevice * paintDevice() override
Implement this function to return the appropriate paint device.
void endPaint() override
This function is called after painting onto the surface has ended.
virtual void render(xcb_window_t window, const QRegion &region, const QPoint &offset)
void beginPaint(const QRegion &) override
This function is called before painting onto the surface begins, with the region in which the paintin...
virtual void recreateImage(QXcbWindow *win, const QSize &size)
QImage toImage() const override
Implemented in subclasses to return the content of the backingstore as a QImage.
bool scroll(const QRegion &area, int dx, int dy) override
Scrolls the given area dx pixels to the right and dy downward; both dx and dy may be negative.
uchar * data() override
Accessor for the bytes of the buffer.
QXcbGraphicsBuffer(QImage *image)
const uchar * data() const override
Accessor for the bytes of the buffer.
int bytesPerLine() const override
Accessor for bytes per line in the graphics buffer.
Origin origin() const override
In origin of the content of the graphics buffer.
bool doLock(AccessTypes access, const QRect &rect) override
This function should be reimplemented by subclasses.
void doUnlock() override
This function should remove all locks set on the buffer.
QXcbConnection * connection() const
Definition qxcbobject.h:17
xcb_connection_t * xcb_connection() const
Definition qxcbobject.h:20
void setConnection(QXcbConnection *connection)
Definition qxcbobject.h:16
void render(xcb_window_t window, const QRegion &region, const QPoint &offset) override
void beginPaint(const QRegion &) override
This function is called before painting onto the surface begins, with the region in which the paintin...
void recreateImage(QXcbWindow *win, const QSize &size) override
#define MAP_FAILED
static void copy_swapped(char *dst, const int dstStride, const QImage &img, const QRect &rect)
void qt_scrollRectInImage(QImage &img, const QRect &rect, const QPoint &offset)
static QImage native_sub_image(QByteArray *buffer, const int dstStride, const QImage &src, const QRect &rect, bool swap)
static quint32 round_up_scanline(quint32 base, quint32 pad)
static void copy_unswapped(char *dst, int dstBytesPerLine, const QImage &img, const QRect &rect)
static size_t imageDataSize(const xcb_image_t *image)
#define Q_XCB_REPLY(call,...)
#define Q_XCB_REPLY_UNCHECKED(call,...)