diff options
Diffstat (limited to 'src/plugins/platforms/xcb/qxcbbackingstore.cpp')
-rw-r--r-- | src/plugins/platforms/xcb/qxcbbackingstore.cpp | 409 |
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 ®ion, const QPoint &offset); void preparePaint(const QRegion ®ion); private: void destroy(); + void ensureGC(xcb_drawable_t dst); + void flushPixmap(const QRegion ®ion); + void setClip(const QRegion ®ion); + 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 ®ion) +{ + 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 ®ion) +{ + 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 ®ion, 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 ®ion) { - // 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 ®ion) { - 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 ®ion) 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 ®ion, 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 |