From eab84914f1a31984f4698bcbca170556a56fd6fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morten=20Johan=20S=C3=B8rvig?= Date: Thu, 27 Sep 2018 12:26:36 +0200 Subject: macOS: make QScreen::grabWindow() fast again MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 17b73b0d2b8 regressed performance by grabbing the entire screen and then copying the grab region. Move back to grabbing a minimal rectangle instead. Also handle multi-screen grabs at different device pixel ratios. Multi-screen grabbing is handled by grabbing each screen individually, and then stitching together the result. The returned pixmap is created using the highest DPR available, upscaling lower resolution parts as needed. Change-Id: I9d45c5f7ec7b342360b745f49ef2939dc588f40b Reviewed-by: Tor Arne Vestbø --- src/plugins/platforms/cocoa/qcocoascreen.mm | 95 +++++++++++++++-------------- 1 file changed, 50 insertions(+), 45 deletions(-) diff --git a/src/plugins/platforms/cocoa/qcocoascreen.mm b/src/plugins/platforms/cocoa/qcocoascreen.mm index 0d6567070e..afe14e623c 100644 --- a/src/plugins/platforms/cocoa/qcocoascreen.mm +++ b/src/plugins/platforms/cocoa/qcocoascreen.mm @@ -428,66 +428,71 @@ QWindow *QCocoaScreen::topLevelAt(const QPoint &point) const return window; } -QPixmap QCocoaScreen::grabWindow(WId window, int x, int y, int width, int height) const +QPixmap QCocoaScreen::grabWindow(WId view, int x, int y, int width, int height) const { - // TODO window should be handled - Q_UNUSED(window) - - const int maxDisplays = 128; // 128 displays should be enough for everyone. + // Determine the grab rect. FIXME: The rect should be bounded by the view's + // geometry, but note that for the pixeltool use case that window will be the + // desktop widgets's view, which currently gets resized to fit one screen + // only, since its NSWindow has the NSWindowStyleMaskTitled flag set. + Q_UNUSED(view); + QRect grabRect = QRect(x, y, width, height); + qCDebug(lcQpaScreen) << "input grab rect" << grabRect; + + // Find which displays to grab from, or all of them if the grab size is unspecified + const int maxDisplays = 128; CGDirectDisplayID displays[maxDisplays]; CGDisplayCount displayCount; - CGRect cgRect; - - if (width < 0 || height < 0) { - // get all displays - cgRect = CGRectInfinite; - } else { - cgRect = CGRectMake(x, y, width, height); - } + CGRect cgRect = (width < 0 || height < 0) ? CGRectInfinite : grabRect.toCGRect(); const CGDisplayErr err = CGGetDisplaysWithRect(cgRect, maxDisplays, displays, &displayCount); - - if (err && displayCount == 0) + if (err || displayCount == 0) return QPixmap(); - // calculate pixmap size - QSize windowSize(width, height); + // If the grab size is not specified, set it to be the bounding box of all screens, if (width < 0 || height < 0) { QRect windowRect; for (uint i = 0; i < displayCount; ++i) { - const CGRect cgRect = CGDisplayBounds(displays[i]); - QRect qRect(cgRect.origin.x, cgRect.origin.y, cgRect.size.width, cgRect.size.height); - windowRect = windowRect.united(qRect); + QRect displayBounds = QRectF::fromCGRect(CGDisplayBounds(displays[i])).toRect(); + windowRect = windowRect.united(displayBounds); } - if (width < 0) - windowSize.setWidth(windowRect.width()); - if (height < 0) - windowSize.setHeight(windowRect.height()); + if (grabRect.width() < 0) + grabRect.setWidth(windowRect.width()); + if (grabRect.height() < 0) + grabRect.setHeight(windowRect.height()); } - const qreal dpr = devicePixelRatio(); - QPixmap windowPixmap(windowSize * dpr); - windowPixmap.setDevicePixelRatio(dpr); - windowPixmap.fill(Qt::transparent); + qCDebug(lcQpaScreen) << "final grab rect" << grabRect << "from" << displayCount << "displays"; + // Grab images from each display + QVector images; + QVector destinations; for (uint i = 0; i < displayCount; ++i) { - const CGRect bounds = CGDisplayBounds(displays[i]); - - // Calculate the position and size of the requested area - QPoint pos(qAbs(bounds.origin.x - x), qAbs(bounds.origin.y - y)); - QSize size(qMin(width, qRound(bounds.size.width)), - qMin(height, qRound(bounds.size.height))); - pos *= dpr; - size *= dpr; - - // Take the whole screen and crop it afterwards, because CGDisplayCreateImageForRect - // has a strange behavior when mixing highDPI and non-highDPI displays - QCFType cgImage = CGDisplayCreateImage(displays[i]); - const QImage image = qt_mac_toQImage(cgImage); - - // Draw into windowPixmap only the requested size - QPainter painter(&windowPixmap); - painter.drawImage(windowPixmap.rect(), image, QRect(pos, size)); + auto display = displays[i]; + QRect displayBounds = QRectF::fromCGRect(CGDisplayBounds(display)).toRect(); + QRect grabBounds = displayBounds.intersected(grabRect); + QRect displayLocalGrabBounds = QRect(QPoint(grabBounds.topLeft() - displayBounds.topLeft()), grabBounds.size()); + QImage displayImage = qt_mac_toQImage(QCFType(CGDisplayCreateImageForRect(display, displayLocalGrabBounds.toCGRect()))); + displayImage.setDevicePixelRatio(displayImage.size().width() / displayLocalGrabBounds.size().width()); + images.append(displayImage); + QRect destBounds = QRect(QPoint(grabBounds.topLeft() - grabRect.topLeft()), grabBounds.size()); + destinations.append(destBounds); + qCDebug(lcQpaScreen) << "grab display" << i << "global" << grabBounds << "local" << displayLocalGrabBounds + << "grab image size" << displayImage.size() << "devicePixelRatio" << displayImage.devicePixelRatio(); } + + // Determine the highest dpr, which becomes the dpr for the returned pixmap. + qreal dpr = 1.0; + for (uint i = 0; i < displayCount; ++i) + dpr = qMax(dpr, images.at(i).devicePixelRatio()); + + // Alocate target pixmap and draw each screen's content + qCDebug(lcQpaScreen) << "Create grap pixmap" << grabRect.size() << "at devicePixelRatio" << dpr; + QPixmap windowPixmap(grabRect.size() * dpr); + windowPixmap.setDevicePixelRatio(dpr); + windowPixmap.fill(Qt::transparent); + QPainter painter(&windowPixmap); + for (uint i = 0; i < displayCount; ++i) + painter.drawImage(destinations.at(i), images.at(i)); + return windowPixmap; } -- cgit v1.2.3