Widgets: Use accelerated scroll when scrolled widget is overlapped

Get region of overlapped widgets and scroll only non-overlapped parts
of image. Next, schedule an update for overlapped widgets region.

This patch improves scrolling performance when scrolled widget has
overlapped widgets.

Common use cases:
- faster scrolling when using "StyleHint::SH_ScrollBar_Transient",
- faster scrolling of zoomed image with semi-transparent thumbnail.

Accelerated scrolling with overlapped widgets is not available when
scale factor is non-integer.

Task-number: QTBUG-64504
Change-Id: I8337d3bc756e50f7d31cdc7979ccf86dc5c3695f
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
This commit is contained in:
Błażej Szczygieł 2017-11-10 23:52:17 +01:00
parent 98ad498a46
commit 5b09346cf4
3 changed files with 81 additions and 23 deletions

View File

@ -1904,19 +1904,21 @@ void QWidgetPrivate::deleteTLSysExtra()
}
/*
Returns \c true if there are widgets above this which overlap with
Returns \c region of widgets above this which overlap with
\a rect, which is in parent's coordinate system (same as crect).
*/
bool QWidgetPrivate::isOverlapped(const QRect &rect) const
QRegion QWidgetPrivate::overlappedRegion(const QRect &rect, bool breakAfterFirst) const
{
Q_Q(const QWidget);
const QWidget *w = q;
QRect r = rect;
QPoint p;
QRegion region;
while (w) {
if (w->isWindow())
return false;
break;
QWidgetPrivate *pd = w->parentWidget()->d_func();
bool above = false;
for (int i = 0; i < pd->children.size(); ++i) {
@ -1928,19 +1930,23 @@ bool QWidgetPrivate::isOverlapped(const QRect &rect) const
continue;
}
if (qRectIntersects(sibling->d_func()->effectiveRectFor(sibling->data->crect), r)) {
const QRect siblingRect = sibling->d_func()->effectiveRectFor(sibling->data->crect);
if (qRectIntersects(siblingRect, r)) {
const QWExtra *siblingExtra = sibling->d_func()->extra;
if (siblingExtra && siblingExtra->hasMask && !sibling->d_func()->graphicsEffect
&& !siblingExtra->mask.translated(sibling->data->crect.topLeft()).intersects(r)) {
continue;
}
return true;
region += siblingRect.translated(-p);
if (breakAfterFirst)
break;
}
}
w = w->parentWidget();
r.translate(pd->data.crect.topLeft());
p += pd->data.crect.topLeft();
}
return false;
return region;
}
void QWidgetPrivate::syncBackingStore()

View File

@ -453,7 +453,7 @@ public:
// ### Qt 4.6: Merge into a template function (after MSVC isn't supported anymore).
void invalidateBuffer(const QRegion &);
void invalidateBuffer(const QRect &);
bool isOverlapped(const QRect&) const;
QRegion overlappedRegion(const QRect &rect, bool breakAfterFirst = false) const;
void syncBackingStore();
void syncBackingStore(const QRegion &region);

View File

@ -59,6 +59,7 @@
#include <private/qgraphicseffect_p.h>
#endif
#include <QtGui/private/qwindow_p.h>
#include <QtGui/private/qhighdpiscaling_p.h>
#include <qpa/qplatformbackingstore.h>
@ -793,6 +794,24 @@ QWidgetBackingStore::~QWidgetBackingStore()
delete dirtyOnScreenWidgets;
}
static QVector<QRect> getSortedRectsToScroll(const QRegion &region, int dx, int dy)
{
QVector<QRect> rects = region.rects();
if (rects.count() > 1) {
std::sort(rects.begin(), rects.end(), [=](const QRect &r1, const QRect &r2) {
if (r1.y() == r2.y()) {
if (dx > 0)
return r1.x() > r2.x();
return r1.x() < r2.x();
}
if (dy > 0)
return r1.y() > r2.y();
return r1.y() < r2.y();
});
}
return rects;
}
//parent's coordinates; move whole rect; update parent and widget
//assume the screen blt has already been done, so we don't need to refresh that part
void QWidgetPrivate::moveRect(const QRect &rect, int dx, int dy)
@ -820,12 +839,12 @@ void QWidgetPrivate::moveRect(const QRect &rect, int dx, int dy)
const QRect parentRect(rect & clipR);
const bool nativeWithTextureChild = textureChildSeen && q->internalWinId();
bool accelerateMove = accelEnv && isOpaque && !nativeWithTextureChild
const bool accelerateMove = accelEnv && isOpaque && !nativeWithTextureChild
#if QT_CONFIG(graphicsview)
// No accelerate move for proxy widgets.
&& !tlw->d_func()->extra->proxyWidget
#endif
&& !isOverlapped(sourceRect) && !isOverlapped(destRect);
;
if (!accelerateMove) {
QRegion parentR(effectiveRectFor(parentRect));
@ -841,18 +860,39 @@ void QWidgetPrivate::moveRect(const QRect &rect, int dx, int dy)
QWidgetBackingStore *wbs = x->backingStoreTracker.data();
QRegion childExpose(newRect & clipR);
QRegion overlappedExpose;
if (sourceRect.isValid() && wbs->bltRect(sourceRect, dx, dy, pw))
childExpose -= destRect;
if (sourceRect.isValid()) {
overlappedExpose = (overlappedRegion(sourceRect) | overlappedRegion(destRect)) & clipR;
const qreal factor = QHighDpiScaling::factor(q->windowHandle());
if (overlappedExpose.isEmpty() || qFloor(factor) == factor) {
const QVector<QRect> rectsToScroll
= getSortedRectsToScroll(QRegion(sourceRect) - overlappedExpose, dx, dy);
for (QRect rect : rectsToScroll) {
if (wbs->bltRect(rect, dx, dy, pw)) {
childExpose -= rect.translated(dx, dy);
}
}
}
childExpose -= overlappedExpose;
}
if (!pw->updatesEnabled())
return;
const bool childUpdatesEnabled = q->updatesEnabled();
if (childUpdatesEnabled && !childExpose.isEmpty()) {
childExpose.translate(-data.crect.topLeft());
wbs->markDirty(childExpose, q);
isMoved = true;
if (childUpdatesEnabled) {
if (!overlappedExpose.isEmpty()) {
overlappedExpose.translate(-data.crect.topLeft());
invalidateBuffer(overlappedExpose);
}
if (!childExpose.isEmpty()) {
childExpose.translate(-data.crect.topLeft());
wbs->markDirty(childExpose, q);
isMoved = true;
}
}
QRegion parentExpose(parentRect);
@ -888,13 +928,12 @@ void QWidgetPrivate::scrollRect(const QRect &rect, int dx, int dy)
static const bool accelEnv = qEnvironmentVariableIntValue("QT_NO_FAST_SCROLL") == 0;
QRect scrollRect = rect & clipRect();
bool overlapped = false;
bool accelerateScroll = accelEnv && isOpaque && !q_func()->testAttribute(Qt::WA_WState_InPaintEvent)
&& !(overlapped = isOverlapped(scrollRect.translated(data.crect.topLeft())));
const QRect clipR = clipRect();
const QRect scrollRect = rect & clipR;
const bool accelerateScroll = accelEnv && isOpaque && !q_func()->testAttribute(Qt::WA_WState_InPaintEvent);
if (!accelerateScroll) {
if (overlapped) {
if (!overlappedRegion(scrollRect.translated(data.crect.topLeft()), true).isEmpty()) {
QRegion region(scrollRect);
subtractOpaqueSiblings(region);
invalidateBuffer(region);
@ -906,12 +945,23 @@ void QWidgetPrivate::scrollRect(const QRect &rect, int dx, int dy)
const QRect destRect = scrollRect.translated(dx, dy) & scrollRect;
const QRect sourceRect = destRect.translated(-dx, -dy);
const QRegion overlappedExpose = (overlappedRegion(scrollRect.translated(data.crect.topLeft())))
.translated(-data.crect.topLeft()) & clipR;
QRegion childExpose(scrollRect);
if (sourceRect.isValid()) {
if (wbs->bltRect(sourceRect, dx, dy, q))
childExpose -= destRect;
const qreal factor = QHighDpiScaling::factor(q->windowHandle());
if (overlappedExpose.isEmpty() || qFloor(factor) == factor) {
const QVector<QRect> rectsToScroll
= getSortedRectsToScroll(QRegion(sourceRect) - overlappedExpose, dx, dy);
for (const QRect &rect : rectsToScroll) {
if (wbs->bltRect(rect, dx, dy, q)) {
childExpose -= rect.translated(dx, dy);
}
}
}
childExpose -= overlappedExpose;
if (inDirtyList) {
if (rect == q->rect()) {
dirty.translate(dx, dy);
@ -928,6 +978,8 @@ void QWidgetPrivate::scrollRect(const QRect &rect, int dx, int dy)
if (!q->updatesEnabled())
return;
if (!overlappedExpose.isEmpty())
invalidateBuffer(overlappedExpose);
if (!childExpose.isEmpty()) {
wbs->markDirty(childExpose, q);
isScrolled = true;