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

source code of qtbase/src/plugins/platforms/xcb/qxcbbackingstore.cpp