From 67444e36592e78d46b757a6c72be1d50bdae19e6 Mon Sep 17 00:00:00 2001 From: Vlad Zagorodniy Date: Thu, 23 May 2019 21:21:11 +0300 Subject: [PATCH] [effects/screenshot] Upload data to xpixmap in chunks Summary: xcb_put_image doesn't handle big images well. Contrary to XPutImage, the caller must manually split the data in such a way that each xcb_put_image request doesn't exceed the maximum request length. Not doing so will result in libxcb shutting down the connection. CCBUG: 338489 CCBUG: 388182 Test Plan: Take a screenshot of an active fullscreen client on a 4K monitor. Reviewers: #kwin, davidedmundson Reviewed By: #kwin, davidedmundson Subscribers: davidedmundson, kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D21370 --- effects/screenshot/screenshot.cpp | 80 +++++++++++++++++++++++++++---- 1 file changed, 70 insertions(+), 10 deletions(-) diff --git a/effects/screenshot/screenshot.cpp b/effects/screenshot/screenshot.cpp index af1903eb76..d6e121a8da 100644 --- a/effects/screenshot/screenshot.cpp +++ b/effects/screenshot/screenshot.cpp @@ -87,6 +87,75 @@ static QImage xPictureToImage(xcb_render_picture_t srcPic, const QRect &geometry } #endif +static QSize pickWindowSize(const QImage &image) +{ + xcb_connection_t *c = effects->xcbConnection(); + + // This will implicitly enable BIG-REQUESTS extension. + const uint32_t maximumRequestSize = xcb_get_maximum_request_length(c); + const xcb_setup_t *setup = xcb_get_setup(c); + + uint32_t requestSize = sizeof(xcb_put_image_request_t); + + // With BIG-REQUESTS extension an additional 32-bit field is inserted into + // the request so we better take it into account. + if (setup->maximum_request_length < maximumRequestSize) { + requestSize += 4; + } + + const uint32_t maximumDataSize = 4 * maximumRequestSize - requestSize; + const uint32_t bytesPerPixel = image.depth() >> 3; + const uint32_t bytesPerLine = image.bytesPerLine(); + + if (image.sizeInBytes() <= maximumDataSize) { + return image.size(); + } + + if (maximumDataSize < bytesPerLine) { + return QSize(maximumDataSize / bytesPerPixel, 1); + } + + return QSize(image.width(), maximumDataSize / bytesPerLine); +} + +static xcb_pixmap_t xpixmapFromImage(const QImage &image) +{ + xcb_connection_t *c = effects->xcbConnection(); + + xcb_pixmap_t pixmap = xcb_generate_id(c); + xcb_gcontext_t gc = xcb_generate_id(c); + + xcb_create_pixmap(c, image.depth(), pixmap, effects->x11RootWindow(), + image.width(), image.height()); + xcb_create_gc(c, gc, pixmap, 0, nullptr); + + const int bytesPerPixel = image.depth() >> 3; + + // Figure out how much data we can send with one invocation of xcb_put_image. + // In contrast to XPutImage, xcb_put_image doesn't implicitly split the data. + const QSize window = pickWindowSize(image); + + for (int i = 0; i < image.height(); i += window.height()) { + const int targetHeight = qMin(image.height() - i, window.height()); + const uint8_t *line = image.scanLine(i); + + for (int j = 0; j < image.width(); j += window.width()) { + const int targetWidth = qMin(image.width() - j, window.width()); + const uint8_t *bytes = line + j * bytesPerPixel; + const uint32_t byteCount = targetWidth * targetHeight * bytesPerPixel; + + xcb_put_image(c, XCB_IMAGE_FORMAT_Z_PIXMAP, pixmap, + gc, targetWidth, targetHeight, j, i, 0, image.depth(), + byteCount, bytes); + } + } + + xcb_flush(c); + xcb_free_gc(c, gc); + + return pixmap; +} + void ScreenShotEffect::paintScreen(int mask, QRegion region, ScreenPaintData &data) { m_cachedOutputGeometry = data.outputGeometry(); @@ -181,16 +250,7 @@ void ScreenShotEffect::postPaintScreen() } if (m_windowMode == WindowMode::Xpixmap) { - const int depth = img.depth(); - xcb_pixmap_t xpix = xcb_generate_id(xcbConnection()); - xcb_create_pixmap(xcbConnection(), depth, xpix, x11RootWindow(), img.width(), img.height()); - - xcb_gcontext_t cid = xcb_generate_id(xcbConnection()); - xcb_create_gc(xcbConnection(), cid, xpix, 0, NULL); - xcb_put_image(xcbConnection(), XCB_IMAGE_FORMAT_Z_PIXMAP, xpix, cid, img.width(), img.height(), - 0, 0, 0, depth, img.byteCount(), img.constBits()); - xcb_free_gc(xcbConnection(), cid); - xcb_flush(xcbConnection()); + const xcb_pixmap_t xpix = xpixmapFromImage(img); emit screenshotCreated(xpix); m_windowMode = WindowMode::NoCapture; } else if (m_windowMode == WindowMode::File) {