summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/xcb/qxcbbackingstore.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/platforms/xcb/qxcbbackingstore.cpp')
-rw-r--r--src/plugins/platforms/xcb/qxcbbackingstore.cpp409
1 files changed, 299 insertions, 110 deletions
diff --git a/src/plugins/platforms/xcb/qxcbbackingstore.cpp b/src/plugins/platforms/xcb/qxcbbackingstore.cpp
index 0b76830d8e..ce9e5d926e 100644
--- a/src/plugins/platforms/xcb/qxcbbackingstore.cpp
+++ b/src/plugins/platforms/xcb/qxcbbackingstore.cpp
@@ -1,31 +1,37 @@
/****************************************************************************
**
-** Copyright (C) 2015 The Qt Company Ltd.
-** Contact: http://www.qt.io/licensing/
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
**
-** $QT_BEGIN_LICENSE:LGPL21$
+** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see http://www.qt.io/terms-conditions. For further
-** information use the contact form at http://www.qt.io/contact-us.
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 2.1 or version 3 as published by the Free
-** Software Foundation and appearing in the file LICENSE.LGPLv21 and
-** LICENSE.LGPLv3 included in the packaging of this file. Please review the
-** following information to ensure the GNU Lesser General Public License
-** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
-** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
-** As a special exception, The Qt Company gives you certain additional
-** rights. These rights are described in The Qt Company LGPL Exception
-** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
@@ -52,6 +58,7 @@
#include <QtGui/private/qhighdpiscaling_p.h>
#include <qpa/qplatformgraphicsbuffer.h>
#include <private/qimage_p.h>
+#include <qendian.h>
#include <algorithm>
QT_BEGIN_NAMESPACE
@@ -62,19 +69,26 @@ public:
QXcbShmImage(QXcbScreen *connection, const QSize &size, uint depth, QImage::Format format);
~QXcbShmImage() { destroy(); }
+ bool scroll(const QRegion &area, int dx, int dy);
+
QImage *image() { return &m_qimage; }
QPlatformGraphicsBuffer *graphicsBuffer() { return m_graphics_buffer; }
QSize size() const { return m_qimage.size(); }
bool hasAlpha() const { return m_hasAlpha; }
+ bool hasShm() const { return m_shm_info.shmaddr != nullptr; }
- void put(xcb_window_t window, const QPoint &dst, const QRect &source);
+ void put(xcb_drawable_t dst, const QRegion &region, const QPoint &offset);
void preparePaint(const QRegion &region);
private:
void destroy();
+ void ensureGC(xcb_drawable_t dst);
+ void flushPixmap(const QRegion &region);
+ void setClip(const QRegion &region);
+
xcb_shm_segment_info_t m_shm_info;
xcb_image_t *m_xcb_image;
@@ -83,9 +97,18 @@ private:
QPlatformGraphicsBuffer *m_graphics_buffer;
xcb_gcontext_t m_gc;
- xcb_window_t m_gc_window;
+ xcb_drawable_t m_gc_drawable;
+
+ // When using shared memory this is the region currently shared with the server
+ QRegion m_dirtyShm;
- QRegion m_dirty;
+ // When not using shared memory, we maintain a server-side pixmap with the backing
+ // store as well as repainted content not yet flushed to the pixmap. We only flush
+ // the regions we need and only when these are marked dirty. This way we can just
+ // do a server-side copy on expose instead of sending the pixels every time
+ xcb_pixmap_t m_xcb_pixmap;
+ QRegion m_pendingFlush;
+ QByteArray m_flushBuffer;
bool m_hasAlpha;
};
@@ -124,18 +147,13 @@ QXcbShmImage::QXcbShmImage(QXcbScreen *screen, const QSize &size, uint depth, QI
: QXcbObject(screen->connection())
, m_graphics_buffer(Q_NULLPTR)
, m_gc(0)
- , m_gc_window(0)
+ , m_gc_drawable(0)
+ , m_xcb_pixmap(0)
{
Q_XCB_NOOP(connection());
- const xcb_setup_t *setup = xcb_get_setup(xcb_connection());
- xcb_format_t *fmt = xcb_setup_pixmap_formats(setup);
- xcb_format_t *fmtend = fmt + xcb_setup_pixmap_formats_length(setup);
- for (; fmt != fmtend; ++fmt)
- if (fmt->depth == depth)
- break;
-
- Q_ASSERT(fmt != fmtend);
+ const xcb_format_t *fmt = connection()->formatForDepth(depth);
+ Q_ASSERT(fmt);
m_xcb_image = xcb_image_create(size.width(), size.height(),
XCB_IMAGE_FORMAT_Z_PIXMAP,
@@ -176,7 +194,7 @@ QXcbShmImage::QXcbShmImage(QXcbScreen *screen, const QSize &size, uint depth, QI
m_xcb_image->data = (uint8_t *)malloc(segmentSize);
} else {
if (shmctl(m_shm_info.shmid, IPC_RMID, 0) == -1)
- qWarning() << "QXcbBackingStore: Error while marking the shared memory segment to be destroyed";
+ qWarning("QXcbBackingStore: Error while marking the shared memory segment to be destroyed");
}
m_hasAlpha = QImage::toPixelFormat(format).alphaUsage() == QPixelFormat::UsesAlpha;
@@ -185,6 +203,48 @@ QXcbShmImage::QXcbShmImage(QXcbScreen *screen, const QSize &size, uint depth, QI
m_qimage = QImage( (uchar*) m_xcb_image->data, m_xcb_image->width, m_xcb_image->height, m_xcb_image->stride, format);
m_graphics_buffer = new QXcbShmGraphicsBuffer(&m_qimage);
+
+ if (!hasShm()) {
+ m_xcb_pixmap = xcb_generate_id(xcb_connection());
+ Q_XCB_CALL(xcb_create_pixmap(xcb_connection(),
+ m_xcb_image->depth,
+ m_xcb_pixmap,
+ screen->screen()->root,
+ m_xcb_image->width, m_xcb_image->height));
+ }
+}
+
+extern void qt_scrollRectInImage(QImage &img, const QRect &rect, const QPoint &offset);
+
+bool QXcbShmImage::scroll(const QRegion &area, int dx, int dy)
+{
+ if (image()->isNull())
+ return false;
+
+ if (hasShm())
+ preparePaint(area);
+
+ const QPoint delta(dx, dy);
+ foreach (const QRect &rect, area.rects())
+ qt_scrollRectInImage(*image(), rect, delta);
+
+ if (m_xcb_pixmap) {
+ flushPixmap(area);
+ ensureGC(m_xcb_pixmap);
+ const QRect bounds(QPoint(0, 0), size());
+ foreach (const QRect &src, area.rects()) {
+ const QRect dst = src.translated(delta).intersected(bounds);
+ Q_XCB_CALL(xcb_copy_area(xcb_connection(),
+ m_xcb_pixmap,
+ m_xcb_pixmap,
+ m_gc,
+ src.x(), src.y(),
+ dst.x(), dst.y(),
+ dst.width(), dst.height()));
+ }
+ }
+
+ return true;
}
void QXcbShmImage::destroy()
@@ -208,95 +268,237 @@ void QXcbShmImage::destroy()
Q_XCB_CALL(xcb_free_gc(xcb_connection(), m_gc));
delete m_graphics_buffer;
m_graphics_buffer = Q_NULLPTR;
+
+ if (m_xcb_pixmap) {
+ Q_XCB_CALL(xcb_free_pixmap(xcb_connection(), m_xcb_pixmap));
+ m_xcb_pixmap = 0;
+ }
}
-void QXcbShmImage::put(xcb_window_t window, const QPoint &target, const QRect &source)
+void QXcbShmImage::ensureGC(xcb_drawable_t dst)
{
- Q_XCB_NOOP(connection());
- if (m_gc_window != window) {
+ if (m_gc_drawable != dst) {
if (m_gc)
Q_XCB_CALL(xcb_free_gc(xcb_connection(), m_gc));
+ static const uint32_t mask = XCB_GC_GRAPHICS_EXPOSURES;
+ static const uint32_t values[] = { 0 };
+
m_gc = xcb_generate_id(xcb_connection());
- Q_XCB_CALL(xcb_create_gc(xcb_connection(), m_gc, window, 0, 0));
+ Q_XCB_CALL(xcb_create_gc(xcb_connection(), m_gc, dst, mask, values));
- m_gc_window = window;
+ m_gc_drawable = dst;
}
+}
- Q_XCB_NOOP(connection());
- if (m_shm_info.shmaddr) {
- xcb_image_shm_put(xcb_connection(),
- window,
- m_gc,
- m_xcb_image,
- m_shm_info,
- source.x(),
- source.y(),
- target.x(),
- target.y(),
- source.width(),
- source.height(),
- false);
+static inline void copy_unswapped(char *dst, int dstBytesPerLine, const QImage &img, const QRect &rect)
+{
+ const uchar *srcData = img.constBits();
+ const int srcBytesPerLine = img.bytesPerLine();
+
+ const int leftOffset = rect.left() * img.depth() >> 3;
+ const int bottom = rect.bottom() + 1;
+
+ for (int yy = rect.top(); yy < bottom; ++yy) {
+ const uchar *src = srcData + yy * srcBytesPerLine + leftOffset;
+ ::memmove(dst, src, dstBytesPerLine);
+ dst += dstBytesPerLine;
+ }
+}
+
+template <class Pixel>
+static inline void copy_swapped(char *dst, const int dstStride, const QImage &img, const QRect &rect)
+{
+ const uchar *srcData = img.constBits();
+ const int srcBytesPerLine = img.bytesPerLine();
+
+ const int left = rect.left();
+ const int width = rect.width();
+ const int bottom = rect.bottom() + 1;
+
+ for (int yy = rect.top(); yy < bottom; ++yy) {
+ Pixel *dstPixels = reinterpret_cast<Pixel *>(dst);
+ const Pixel *srcPixels = reinterpret_cast<const Pixel *>(srcData + yy * srcBytesPerLine) + left;
+
+ for (int i = 0; i < width; ++i)
+ dstPixels[i] = qbswap<Pixel>(*srcPixels++);
+
+ dst += dstStride;
+ }
+}
+
+static QImage native_sub_image(QByteArray *buffer, const int dstStride, const QImage &src, const QRect &rect, bool swap)
+{
+ if (!swap && src.rect() == rect && src.bytesPerLine() == dstStride)
+ return src;
+
+ buffer->resize(rect.height() * dstStride);
+
+ if (swap) {
+ switch (src.depth()) {
+ case 32:
+ copy_swapped<quint32>(buffer->data(), dstStride, src, rect);
+ break;
+ case 16:
+ copy_swapped<quint16>(buffer->data(), dstStride, src, rect);
+ break;
+ }
} else {
- // If we upload the whole image in a single chunk, the result might be
- // larger than the server's maximum request size and stuff breaks.
- // To work around that, we upload the image in chunks where each chunk
- // is small enough for a single request.
- int src_x = source.x();
- int src_y = source.y();
- int target_x = target.x();
- int target_y = target.y();
- int width = source.width();
- int height = source.height();
+ copy_unswapped(buffer->data(), dstStride, src, rect);
+ }
+
+ return QImage(reinterpret_cast<const uchar *>(buffer->constData()), rect.width(), rect.height(), dstStride, src.format());
+}
+
+static inline quint32 round_up_scanline(quint32 base, quint32 pad)
+{
+ return (base + pad - 1) & -pad;
+}
+
+void QXcbShmImage::flushPixmap(const QRegion &region)
+{
+ const QVector<QRect> rects = m_pendingFlush.intersected(region).rects();
+ m_pendingFlush -= region;
+
+ xcb_image_t xcb_subimage;
+ memset(&xcb_subimage, 0, sizeof(xcb_image_t));
+
+ xcb_subimage.format = m_xcb_image->format;
+ xcb_subimage.scanline_pad = m_xcb_image->scanline_pad;
+ xcb_subimage.depth = m_xcb_image->depth;
+ xcb_subimage.bpp = m_xcb_image->bpp;
+ xcb_subimage.unit = m_xcb_image->unit;
+ xcb_subimage.plane_mask = m_xcb_image->plane_mask;
+ xcb_subimage.byte_order = (xcb_image_order_t) connection()->setup()->image_byte_order;
+ xcb_subimage.bit_order = m_xcb_image->bit_order;
+
+ const bool needsByteSwap = xcb_subimage.byte_order != m_xcb_image->byte_order;
+ for (const QRect &rect : rects) {
// We must make sure that each request is not larger than max_req_size.
// Each request takes req_size + m_xcb_image->stride * height bytes.
- uint32_t max_req_size = xcb_get_maximum_request_length(xcb_connection());
- uint32_t req_size = sizeof(xcb_put_image_request_t);
- int rows_per_put = (max_req_size - req_size) / m_xcb_image->stride;
+ static const uint32_t req_size = sizeof(xcb_put_image_request_t);
+ const uint32_t max_req_size = xcb_get_maximum_request_length(xcb_connection());
+ const int rows_per_put = (max_req_size - req_size) / m_xcb_image->stride;
// This assert could trigger if a single row has more pixels than fit in
// a single PutImage request. However, max_req_size is guaranteed to be
// at least 16384 bytes. That should be enough for quite large images.
Q_ASSERT(rows_per_put > 0);
- // Convert the image to the native byte order.
- xcb_image_t *converted_image = xcb_image_native(xcb_connection(), m_xcb_image, 1);
+ // If we upload the whole image in a single chunk, the result might be
+ // larger than the server's maximum request size and stuff breaks.
+ // To work around that, we upload the image in chunks where each chunk
+ // is small enough for a single request.
+ const int x = rect.x();
+ int y = rect.y();
+ const int width = rect.width();
+ int height = rect.height();
while (height > 0) {
- int rows = std::min(height, rows_per_put);
+ const int rows = std::min(height, rows_per_put);
+ const QRect subRect(x, y, width, rows);
+ const quint32 stride = round_up_scanline(width * m_qimage.depth(), xcb_subimage.scanline_pad) >> 3;
+ const QImage subImage = native_sub_image(&m_flushBuffer, stride, m_qimage, subRect, needsByteSwap);
+
+ xcb_subimage.width = width;
+ xcb_subimage.height = rows;
+ xcb_subimage.data = const_cast<uint8_t *>(subImage.constBits());
+ xcb_image_annotate(&xcb_subimage);
- xcb_image_t *subimage = xcb_image_subimage(converted_image, src_x, src_y, width, rows,
- 0, 0, 0);
xcb_image_put(xcb_connection(),
- window,
+ m_xcb_pixmap,
m_gc,
- subimage,
- target_x,
- target_y,
+ &xcb_subimage,
+ x,
+ y,
0);
- xcb_image_destroy(subimage);
-
- src_y += rows;
- target_y += rows;
+ y += rows;
height -= rows;
}
+ }
+}
+
+void QXcbShmImage::setClip(const QRegion &region)
+{
+ if (region.isEmpty()) {
+ static const uint32_t mask = XCB_GC_CLIP_MASK;
+ static const uint32_t values[] = { XCB_NONE };
+ Q_XCB_CALL(xcb_change_gc(xcb_connection(),
+ m_gc,
+ mask,
+ values));
+ } else {
+ const QVector<QRect> qrects = region.rects();
+ QVector<xcb_rectangle_t> xcb_rects(qrects.size());
+
+ for (int i = 0; i < qrects.size(); i++) {
+ xcb_rects[i].x = qrects[i].x();
+ xcb_rects[i].y = qrects[i].y();
+ xcb_rects[i].width = qrects[i].width();
+ xcb_rects[i].height = qrects[i].height();
+ }
- if (converted_image != m_xcb_image)
- xcb_image_destroy(converted_image);
+ Q_XCB_CALL(xcb_set_clip_rectangles(xcb_connection(),
+ XCB_CLIP_ORDERING_YX_BANDED,
+ m_gc,
+ 0, 0,
+ xcb_rects.size(), xcb_rects.constData()));
}
+}
+
+void QXcbShmImage::put(xcb_drawable_t dst, const QRegion &region, const QPoint &offset)
+{
Q_XCB_NOOP(connection());
- m_dirty = m_dirty | source;
+ ensureGC(dst);
+ setClip(region);
+
+ const QRect bounds = region.boundingRect();
+ const QPoint target = bounds.topLeft();
+ const QRect source = bounds.translated(offset);
+
+ if (hasShm()) {
+ Q_XCB_CALL(xcb_shm_put_image(xcb_connection(),
+ dst,
+ m_gc,
+ m_xcb_image->width,
+ m_xcb_image->height,
+ source.x(), source.y(),
+ source.width(), source.height(),
+ target.x(), target.y(),
+ m_xcb_image->depth,
+ m_xcb_image->format,
+ 0, // send event?
+ m_shm_info.shmseg,
+ m_xcb_image->data - m_shm_info.shmaddr));
+ m_dirtyShm |= region.translated(offset);
+ } else {
+ flushPixmap(region);
+ Q_XCB_CALL(xcb_copy_area(xcb_connection(),
+ m_xcb_pixmap,
+ dst,
+ m_gc,
+ source.x(), source.y(),
+ target.x(), target.y(),
+ source.width(), source.height()));
+ }
+
+ setClip(QRegion());
+ Q_XCB_NOOP(connection());
}
void QXcbShmImage::preparePaint(const QRegion &region)
{
- // to prevent X from reading from the image region while we're writing to it
- if (m_dirty.intersects(region)) {
- connection()->sync();
- m_dirty = QRegion();
+ if (hasShm()) {
+ // to prevent X from reading from the image region while we're writing to it
+ if (m_dirtyShm.intersects(region)) {
+ connection()->sync();
+ m_dirtyShm = QRegion();
+ }
+ } else {
+ m_pendingFlush |= region;
}
}
@@ -322,19 +524,16 @@ QPaintDevice *QXcbBackingStore::paintDevice()
void QXcbBackingStore::beginPaint(const QRegion &region)
{
- if (!m_image && !m_size.isEmpty())
- resize(m_size, QRegion());
-
if (!m_image)
return;
- m_size = QSize();
- m_paintRegion = region;
- m_image->preparePaint(m_paintRegion);
+
+ m_paintRegions.push(region);
+ m_image->preparePaint(region);
if (m_image->hasAlpha()) {
QPainter p(paintDevice());
p.setCompositionMode(QPainter::CompositionMode_Source);
- const QVector<QRect> rects = m_paintRegion.rects();
+ const QVector<QRect> rects = region.rects();
const QColor blank = Qt::transparent;
for (QVector<QRect>::const_iterator it = rects.begin(); it != rects.end(); ++it) {
p.fillRect(*it, blank);
@@ -344,13 +543,21 @@ void QXcbBackingStore::beginPaint(const QRegion &region)
void QXcbBackingStore::endPaint()
{
+ if (Q_UNLIKELY(m_paintRegions.isEmpty())) {
+ qWarning("%s: paint regions empty!", Q_FUNC_INFO);
+ return;
+ }
+
+ const QRegion region = m_paintRegions.pop();
+ m_image->preparePaint(region);
+
QXcbWindow *platformWindow = static_cast<QXcbWindow *>(window()->handle());
if (!platformWindow || !platformWindow->imageNeedsRgbSwap())
return;
// Slow path: the paint device was m_rgbImage. Now copy with swapping red
// and blue into m_image.
- const QVector<QRect> rects = m_paintRegion.rects();
+ const QVector<QRect> rects = region.rects();
if (rects.isEmpty())
return;
QPainter p(m_image->image());
@@ -396,11 +603,7 @@ void QXcbBackingStore::flush(QWindow *window, const QRegion &region, const QPoin
return;
}
- QVector<QRect> rects = clipped.rects();
- for (int i = 0; i < rects.size(); ++i) {
- QRect rect = QRect(rects.at(i).topLeft(), rects.at(i).size());
- m_image->put(platformWindow->xcb_window(), rect.topLeft(), rect.translated(offset));
- }
+ m_image->put(platformWindow->xcb_window(), clipped, offset);
Q_XCB_NOOP(connection());
@@ -434,8 +637,7 @@ void QXcbBackingStore::resize(const QSize &size, const QRegion &)
return;
Q_XCB_NOOP(connection());
-
- QXcbScreen *screen = window()->screen() ? static_cast<QXcbScreen *>(window()->screen()->handle()) : 0;
+ QXcbScreen *screen = static_cast<QXcbScreen *>(window()->screen()->handle());
QPlatformWindow *pw = window()->handle();
if (!pw) {
window()->create();
@@ -444,11 +646,6 @@ void QXcbBackingStore::resize(const QSize &size, const QRegion &)
QXcbWindow* win = static_cast<QXcbWindow *>(pw);
delete m_image;
- if (!screen) {
- m_image = 0;
- m_size = size;
- return;
- }
m_image = new QXcbShmImage(screen, size, win->depth(), win->imageFormat());
// Slow path for bgr888 VNC: Create an additional image, paint into that and
// swap R and B while copying to m_image after each paint.
@@ -458,20 +655,12 @@ void QXcbBackingStore::resize(const QSize &size, const QRegion &)
Q_XCB_NOOP(connection());
}
-extern void qt_scrollRectInImage(QImage &img, const QRect &rect, const QPoint &offset);
-
bool QXcbBackingStore::scroll(const QRegion &area, int dx, int dy)
{
- if (!m_image || m_image->image()->isNull())
- return false;
-
- m_image->preparePaint(area);
+ if (m_image)
+ return m_image->scroll(area, dx, dy);
- QPoint delta(dx, dy);
- const QVector<QRect> rects = area.rects();
- for (int i = 0; i < rects.size(); ++i)
- qt_scrollRectInImage(*m_image->image(), rects.at(i), delta);
- return true;
+ return false;
}
QT_END_NAMESPACE