From d43044ff73bb927baf58eff7de6e23362a303b73 Mon Sep 17 00:00:00 2001 From: "nduca@chromium.org" Date: Tue, 31 Jan 2012 22:57:20 +0000 Subject: [PATCH] [chromium] Import PaintAggregator https://bugs.webkit.org/show_bug.cgi?id=53715 Reviewed by Darin Fisher. * WebKit.gyp: * WebKit.gypi: * src/painting/PaintAggregator.cpp: Added. (WebKit::calculateArea): (WebKit::subtractIntersection): (WebKit::sharesEdge): (WebKit::PaintAggregator::PendingUpdate::PendingUpdate): (WebKit::PaintAggregator::PendingUpdate::~PendingUpdate): (WebKit::PaintAggregator::PendingUpdate::calculateScrollDamage): (WebKit::PaintAggregator::PendingUpdate::calculatePaintBounds): (WebKit::PaintAggregator::hasPendingUpdate): (WebKit::PaintAggregator::clearPendingUpdate): (WebKit::PaintAggregator::popPendingUpdate): (WebKit::PaintAggregator::invalidateRect): (WebKit::PaintAggregator::scrollRect): (WebKit::PaintAggregator::scrollPaintRect): (WebKit::PaintAggregator::shouldInvalidateScrollRect): (WebKit::PaintAggregator::invalidateScrollRect): (WebKit::PaintAggregator::combinePaintRects): * src/painting/PaintAggregator.h: Added. * tests/PaintAggregatorTest.cpp: Added. (WebKit::TEST): git-svn-id: http://svn.webkit.org/repository/webkit/trunk@106399 268f45cc-cd09-0410-ab3c-d52691b4dbfc --- Source/WebKit/chromium/ChangeLog | 30 ++ Source/WebKit/chromium/WebKit.gyp | 2 + Source/WebKit/chromium/WebKit.gypi | 1 + .../chromium/src/painting/PaintAggregator.cpp | 374 ++++++++++++++++ .../WebKit/chromium/src/painting/PaintAggregator.h | 92 ++++ .../WebKit/chromium/tests/PaintAggregatorTest.cpp | 493 +++++++++++++++++++++ 6 files changed, 992 insertions(+) create mode 100644 Source/WebKit/chromium/src/painting/PaintAggregator.cpp create mode 100644 Source/WebKit/chromium/src/painting/PaintAggregator.h create mode 100644 Source/WebKit/chromium/tests/PaintAggregatorTest.cpp diff --git a/Source/WebKit/chromium/ChangeLog b/Source/WebKit/chromium/ChangeLog index 51f1b3d..45820aa 100644 --- a/Source/WebKit/chromium/ChangeLog +++ b/Source/WebKit/chromium/ChangeLog @@ -1,3 +1,33 @@ +2012-01-31 Nat Duca + + [chromium] Import PaintAggregator + https://bugs.webkit.org/show_bug.cgi?id=53715 + + Reviewed by Darin Fisher. + + * WebKit.gyp: + * WebKit.gypi: + * src/painting/PaintAggregator.cpp: Added. + (WebKit::calculateArea): + (WebKit::subtractIntersection): + (WebKit::sharesEdge): + (WebKit::PaintAggregator::PendingUpdate::PendingUpdate): + (WebKit::PaintAggregator::PendingUpdate::~PendingUpdate): + (WebKit::PaintAggregator::PendingUpdate::calculateScrollDamage): + (WebKit::PaintAggregator::PendingUpdate::calculatePaintBounds): + (WebKit::PaintAggregator::hasPendingUpdate): + (WebKit::PaintAggregator::clearPendingUpdate): + (WebKit::PaintAggregator::popPendingUpdate): + (WebKit::PaintAggregator::invalidateRect): + (WebKit::PaintAggregator::scrollRect): + (WebKit::PaintAggregator::scrollPaintRect): + (WebKit::PaintAggregator::shouldInvalidateScrollRect): + (WebKit::PaintAggregator::invalidateScrollRect): + (WebKit::PaintAggregator::combinePaintRects): + * src/painting/PaintAggregator.h: Added. + * tests/PaintAggregatorTest.cpp: Added. + (WebKit::TEST): + 2012-01-31 Fady Samuel [Chromium] ChromeClientImpl::dispatchViewportPropertiesDidChange is repeatedly called in Google News diff --git a/Source/WebKit/chromium/WebKit.gyp b/Source/WebKit/chromium/WebKit.gyp index 71e57e3..1dc2957 100644 --- a/Source/WebKit/chromium/WebKit.gyp +++ b/Source/WebKit/chromium/WebKit.gyp @@ -445,6 +445,8 @@ 'src/NotificationPresenterImpl.h', 'src/NotificationPresenterImpl.cpp', 'src/painting/GraphicsContextBuilder.h', + 'src/painting/PaintAggregator.h', + 'src/painting/PaintAggregator.cpp', 'src/PageOverlay.cpp', 'src/PageOverlay.h', 'src/PageOverlayList.cpp', diff --git a/Source/WebKit/chromium/WebKit.gypi b/Source/WebKit/chromium/WebKit.gypi index 84ecfb8..e15fbfc 100644 --- a/Source/WebKit/chromium/WebKit.gypi +++ b/Source/WebKit/chromium/WebKit.gypi @@ -101,6 +101,7 @@ 'tests/KURLTest.cpp', 'tests/LayerChromiumTest.cpp', 'tests/LayerTextureUpdaterTest.cpp', + 'tests/PaintAggregatorTest.cpp', 'tests/PODArenaTest.cpp', 'tests/PODIntervalTreeTest.cpp', 'tests/PODRedBlackTreeTest.cpp', diff --git a/Source/WebKit/chromium/src/painting/PaintAggregator.cpp b/Source/WebKit/chromium/src/painting/PaintAggregator.cpp new file mode 100644 index 0000000..2f7e4c7 --- /dev/null +++ b/Source/WebKit/chromium/src/painting/PaintAggregator.cpp @@ -0,0 +1,374 @@ +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "PaintAggregator.h" + +#include "WebKit.h" +#include "platform/WebKitPlatformSupport.h" + +using namespace WebCore; + +namespace WebKit { + +// ---------------------------------------------------------------------------- +// ALGORITHM NOTES +// +// We attempt to maintain a scroll rect in the presence of invalidations that +// are contained within the scroll rect. If an invalidation crosses a scroll +// rect, then we just treat the scroll rect as an invalidation rect. +// +// For invalidations performed prior to scrolling and contained within the +// scroll rect, we offset the invalidation rects to account for the fact that +// the consumer will perform scrolling before painting. +// +// We only support scrolling along one axis at a time. A diagonal scroll will +// therefore be treated as an invalidation. +// ---------------------------------------------------------------------------- + +// If the combined area of paint rects contained within the scroll rect grows +// too large, then we might as well just treat the scroll rect as a paint rect. +// This constant sets the max ratio of paint rect area to scroll rect area that +// we will tolerate before dograding the scroll into a repaint. +static const float maxRedundantPaintToScrollArea = 0.8f; + +// The maximum number of paint rects. If we exceed this limit, then we'll +// start combining paint rects (see CombinePaintRects). This limiting is +// important since the WebKit code associated with deciding what to paint given +// a paint rect can be significant. +static const size_t maxPaintRects = 5; + +// If the combined area of paint rects divided by the area of the union of all +// paint rects exceeds this threshold, then we will combine the paint rects. +static const float maxPaintRectsAreaRatio = 0.7f; + +static int calculateArea(const IntRect& rect) +{ + return rect.size().width() * rect.size().height(); +} + +// Subtracts out the intersection of |a| and |b| from |a|, assuming |b| fully +// overlaps with |a| in either the x- or y-direction. If there is no full +// overlap, then |a| is returned. +static IntRect subtractIntersection(const IntRect& a, const IntRect& b) +{ + // boundary cases: + if (!a.intersects(b)) + return a; + if (b.contains(a)) + return IntRect(); + + int rx = a.x(); + int ry = a.y(); + int rr = a.maxX(); + int rb = a.maxY(); + + if (b.y() <= a.y() && b.maxY() >= a.maxY()) { + // complete intersection in the y-direction + if (b.x() <= a.x()) + rx = b.maxX(); + else + rr = b.x(); + } else if (b.x() <= a.x() && b.maxX() >= a.maxX()) { + // complete intersection in the x-direction + if (b.y() <= a.y()) + ry = b.maxY(); + else + rb = b.y(); + } + return IntRect(rx, ry, rr - rx, rb - ry); +} + +// Returns true if |a| and |b| share an entire edge (i.e., same width or same +// height), and the rectangles do not overlap. +static bool sharesEdge(const IntRect& a, const IntRect& b) +{ + return (a.y() == b.y() && a.height() == b.height() && (a.x() == b.maxX() || a.maxX() == b.x())) + || (a.x() == b.x() && a.width() == b.width() && (a.y() == b.maxY() || a.maxY() == b.y())); +} + +PaintAggregator::PendingUpdate::PendingUpdate() +{ +} + +PaintAggregator::PendingUpdate::~PendingUpdate() +{ +} + +IntRect PaintAggregator::PendingUpdate::calculateScrollDamage() const +{ + // Should only be scrolling in one direction at a time. + ASSERT(!(scrollDelta.x() && scrollDelta.y())); + + IntRect damagedRect; + + // Compute the region we will expose by scrolling, and paint that into a + // shared memory section. + if (scrollDelta.x()) { + int dx = scrollDelta.x(); + damagedRect.setY(scrollRect.y()); + damagedRect.setHeight(scrollRect.height()); + if (dx > 0) { + damagedRect.setX(scrollRect.x()); + damagedRect.setWidth(dx); + } else { + damagedRect.setX(scrollRect.maxX() + dx); + damagedRect.setWidth(-dx); + } + } else { + int dy = scrollDelta.y(); + damagedRect.setX(scrollRect.x()); + damagedRect.setWidth(scrollRect.width()); + if (dy > 0) { + damagedRect.setY(scrollRect.y()); + damagedRect.setHeight(dy); + } else { + damagedRect.setY(scrollRect.maxY() + dy); + damagedRect.setHeight(-dy); + } + } + + // In case the scroll offset exceeds the width/height of the scroll rect + return intersection(scrollRect, damagedRect); +} + +IntRect PaintAggregator::PendingUpdate::calculatePaintBounds() const +{ + IntRect bounds; + for (size_t i = 0; i < paintRects.size(); ++i) + bounds.unite(paintRects[i]); + return bounds; +} + +bool PaintAggregator::hasPendingUpdate() const +{ + return !m_update.scrollRect.isEmpty() || !m_update.paintRects.isEmpty(); +} + +void PaintAggregator::clearPendingUpdate() +{ + m_update = PendingUpdate(); +} + +void PaintAggregator::popPendingUpdate(PendingUpdate* update) +{ + // Combine paint rects if their combined area is not sufficiently less than + // the area of the union of all paint rects. We skip this if there is a + // scroll rect since scrolling benefits from smaller paint rects. + if (m_update.scrollRect.isEmpty() && m_update.paintRects.size() > 1) { + int paintArea = 0; + IntRect unionRect; + for (size_t i = 0; i < m_update.paintRects.size(); ++i) { + paintArea += calculateArea(m_update.paintRects[i]); + unionRect.unite(m_update.paintRects[i]); + } + int unionArea = calculateArea(unionRect); + if (float(paintArea) / float(unionArea) > maxPaintRectsAreaRatio) + combinePaintRects(); + } + *update = m_update; + clearPendingUpdate(); +} + +void PaintAggregator::invalidateRect(const IntRect& rect) +{ + // Combine overlapping paints using smallest bounding box. + for (size_t i = 0; i < m_update.paintRects.size(); ++i) { + const IntRect& existingRect = m_update.paintRects[i]; + if (existingRect.contains(rect)) // Optimize out redundancy. + return; + if (rect.intersects(existingRect) || sharesEdge(rect, existingRect)) { + // Re-invalidate in case the union intersects other paint rects. + IntRect combinedRect = unionRect(existingRect, rect); + m_update.paintRects.remove(i); + invalidateRect(combinedRect); + return; + } + } + + // Add a non-overlapping paint. + m_update.paintRects.append(rect); + + // If the new paint overlaps with a scroll, then it forces an invalidation of + // the scroll. If the new paint is contained by a scroll, then trim off the + // scroll damage to avoid redundant painting. + if (!m_update.scrollRect.isEmpty()) { + if (shouldInvalidateScrollRect(rect)) + invalidateScrollRect(); + else if (m_update.scrollRect.contains(rect)) { + m_update.paintRects[m_update.paintRects.size() - 1] = + subtractIntersection(rect, m_update.calculateScrollDamage()); + if (m_update.paintRects[m_update.paintRects.size() - 1].isEmpty()) + m_update.paintRects.remove(m_update.paintRects.size() - 1); + } + } + + if (m_update.paintRects.size() > maxPaintRects) + combinePaintRects(); + + // Track how large the paintRects vector grows during an invalidation + // sequence. Note: A subsequent invalidation may end up being combined + // with all existing paints, which means that tracking the size of + // paintRects at the time when popPendingUpdate() is called may mask + // certain performance problems. + webKitPlatformSupport()->histogramCustomCounts("MPArch.RW_IntermediatePaintRectCount", + m_update.paintRects.size(), 1, 100, 50); +} + +void PaintAggregator::scrollRect(int dx, int dy, const IntRect& clipRect) +{ + // We only support scrolling along one axis at a time. + if (dx && dy) { + invalidateRect(clipRect); + return; + } + + // We can only scroll one rect at a time. + if (!m_update.scrollRect.isEmpty() && m_update.scrollRect != clipRect) { + invalidateRect(clipRect); + return; + } + + // Again, we only support scrolling along one axis at a time. Make sure this + // update doesn't scroll on a different axis than any existing one. + if ((dx && m_update.scrollDelta.y()) || (dy && m_update.scrollDelta.x())) { + invalidateRect(clipRect); + return; + } + + // The scroll rect is new or isn't changing (though the scroll amount may + // be changing). + m_update.scrollRect = clipRect; + m_update.scrollDelta.move(dx, dy); + + // We might have just wiped out a pre-existing scroll. + if (m_update.scrollDelta == IntPoint()) { + m_update.scrollRect = IntRect(); + return; + } + + // Adjust any contained paint rects and check for any overlapping paints. + for (size_t i = 0; i < m_update.paintRects.size(); ++i) { + if (m_update.scrollRect.contains(m_update.paintRects[i])) { + m_update.paintRects[i] = scrollPaintRect(m_update.paintRects[i], dx, dy); + // The rect may have been scrolled out of view. + if (m_update.paintRects[i].isEmpty()) { + m_update.paintRects.remove(i); + i--; + } + } else if (m_update.scrollRect.intersects(m_update.paintRects[i])) { + invalidateScrollRect(); + return; + } + } + + // If the new scroll overlaps too much with contained paint rects, then force + // an invalidation of the scroll. + if (shouldInvalidateScrollRect(IntRect())) + invalidateScrollRect(); +} + +IntRect PaintAggregator::scrollPaintRect(const IntRect& paintRect, int dx, int dy) const +{ + IntRect result = paintRect; + + result.move(dx, dy); + result = intersection(m_update.scrollRect, result); + + // Subtract out the scroll damage rect to avoid redundant painting. + return subtractIntersection(result, m_update.calculateScrollDamage()); +} + +bool PaintAggregator::shouldInvalidateScrollRect(const IntRect& rect) const +{ + if (!rect.isEmpty()) { + if (!m_update.scrollRect.intersects(rect)) + return false; + + if (!m_update.scrollRect.contains(rect)) + return true; + } + + // Check if the combined area of all contained paint rects plus this new + // rect comes too close to the area of the scrollRect. If so, then we + // might as well invalidate the scroll rect. + + int paintArea = calculateArea(rect); + for (size_t i = 0; i < m_update.paintRects.size(); ++i) { + const IntRect& existingRect = m_update.paintRects[i]; + if (m_update.scrollRect.contains(existingRect)) + paintArea += calculateArea(existingRect); + } + int scrollArea = calculateArea(m_update.scrollRect); + if (float(paintArea) / float(scrollArea) > maxRedundantPaintToScrollArea) + return true; + + return false; +} + +void PaintAggregator::invalidateScrollRect() +{ + IntRect scrollRect = m_update.scrollRect; + m_update.scrollRect = IntRect(); + m_update.scrollDelta = IntPoint(); + invalidateRect(scrollRect); +} + +void PaintAggregator::combinePaintRects() +{ + // Combine paint rects do to at most two rects: one inside the scrollRect + // and one outside the scrollRect. If there is no scrollRect, then just + // use the smallest bounding box for all paint rects. + // + // NOTE: This is a fairly simple algorithm. We could get fancier by only + // combining two rects to get us under the maxPaintRects limit, but if we + // reach this method then it means we're hitting a rare case, so there's no + // need to over-optimize it. + // + if (m_update.scrollRect.isEmpty()) { + IntRect bounds = m_update.calculatePaintBounds(); + m_update.paintRects.clear(); + m_update.paintRects.append(bounds); + } else { + IntRect inner, outer; + for (size_t i = 0; i < m_update.paintRects.size(); ++i) { + const IntRect& existingRect = m_update.paintRects[i]; + if (m_update.scrollRect.contains(existingRect)) + inner.unite(existingRect); + else + outer.unite(existingRect); + } + m_update.paintRects.clear(); + m_update.paintRects.append(inner); + m_update.paintRects.append(outer); + } +} + +} // namespace WebKit diff --git a/Source/WebKit/chromium/src/painting/PaintAggregator.h b/Source/WebKit/chromium/src/painting/PaintAggregator.h new file mode 100644 index 0000000..f083e40 --- /dev/null +++ b/Source/WebKit/chromium/src/painting/PaintAggregator.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef PaintAggregator_h +#define PaintAggregator_h + +#include "IntPoint.h" +#include "IntRect.h" +#include + +namespace WebKit { + +// This class is responsible for aggregating multiple invalidation and scroll +// commands to produce a scroll and repaint sequence. +class PaintAggregator { +public: + // This structure describes an aggregation of invalidateRect and scrollRect + // calls. If |scrollRect| is non-empty, then that rect should be scrolled + // by the amount specified by |scrollDelta|. If |paintRects| is non-empty, + // then those rects should be repainted. If |scrollRect| and |paintRects| + // are non-empty, then scrolling should be performed before repainting. + // |scrollDelta| can only specify scrolling in one direction (i.e., the x + // and y members cannot both be non-zero). + struct PendingUpdate { + PendingUpdate(); + ~PendingUpdate(); + + // Returns the rect damaged by scrolling within |scrollRect| by + // |scrollDelta|. This rect must be repainted. + WebCore::IntRect calculateScrollDamage() const; + + // Returns the smallest rect containing all paint rects. + WebCore::IntRect calculatePaintBounds() const; + + WebCore::IntPoint scrollDelta; + WebCore::IntRect scrollRect; + WTF::Vector paintRects; + }; + + // There is a PendingUpdate if invalidateRect or scrollRect were called and + // ClearPendingUpdate was not called. + bool hasPendingUpdate() const; + void clearPendingUpdate(); + + // Fills |update| and clears the pending update. + void popPendingUpdate(PendingUpdate*); + + // The given rect should be repainted. + void invalidateRect(const WebCore::IntRect&); + + // The given rect should be scrolled by the given amounts. + void scrollRect(int dx, int dy, const WebCore::IntRect& clipRect); + +private: + WebCore::IntRect scrollPaintRect(const WebCore::IntRect& paintRect, int dx, int dy) const; + bool shouldInvalidateScrollRect(const WebCore::IntRect&) const; + void invalidateScrollRect(); + void combinePaintRects(); + + PendingUpdate m_update; +}; + +} // namespace WebKit + +#endif diff --git a/Source/WebKit/chromium/tests/PaintAggregatorTest.cpp b/Source/WebKit/chromium/tests/PaintAggregatorTest.cpp new file mode 100644 index 0000000..fcf374b4 --- /dev/null +++ b/Source/WebKit/chromium/tests/PaintAggregatorTest.cpp @@ -0,0 +1,493 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "painting/PaintAggregator.h" + +#include + +using namespace WebCore; +using namespace WebKit; + +namespace { + +TEST(PaintAggregator, InitialState) +{ + PaintAggregator greg; + EXPECT_FALSE(greg.hasPendingUpdate()); +} + +TEST(PaintAggregator, SingleInvalidation) +{ + PaintAggregator greg; + + IntRect rect(2, 4, 10, 16); + greg.invalidateRect(rect); + + EXPECT_TRUE(greg.hasPendingUpdate()); + PaintAggregator::PendingUpdate update; + greg.popPendingUpdate(&update); + + EXPECT_TRUE(update.scrollRect.isEmpty()); + ASSERT_EQ(1U, update.paintRects.size()); + + EXPECT_EQ(rect, update.paintRects[0]); +} + +TEST(PaintAggregator, DoubleDisjointInvalidation) +{ + PaintAggregator greg; + + IntRect r1(2, 4, 2, 40); + IntRect r2(4, 2, 40, 2); + + greg.invalidateRect(r1); + greg.invalidateRect(r2); + + IntRect expectedBounds = unionRect(r1, r2); + + EXPECT_TRUE(greg.hasPendingUpdate()); + PaintAggregator::PendingUpdate update; + greg.popPendingUpdate(&update); + + EXPECT_TRUE(update.scrollRect.isEmpty()); + EXPECT_EQ(2U, update.paintRects.size()); + + EXPECT_EQ(expectedBounds, update.calculatePaintBounds()); +} + +TEST(PaintAggregator, DisjointInvalidationsCombined) +{ + PaintAggregator greg; + + // Make the rectangles such that they don't overlap but cover a very large + // percentage of the area of covered by their union. This is so we're not + // very sensitive to the combining heuristic in the paint aggregator. + IntRect r1(2, 4, 2, 1000); + IntRect r2(5, 2, 2, 1000); + + greg.invalidateRect(r1); + greg.invalidateRect(r2); + + IntRect expectedBounds = unionRect(r1, r2); + + EXPECT_TRUE(greg.hasPendingUpdate()); + PaintAggregator::PendingUpdate update; + greg.popPendingUpdate(&update); + + EXPECT_TRUE(update.scrollRect.isEmpty()); + ASSERT_EQ(1U, update.paintRects.size()); + + EXPECT_EQ(expectedBounds, update.paintRects[0]); +} + +TEST(PaintAggregator, SingleScroll) +{ + PaintAggregator greg; + + IntRect rect(1, 2, 3, 4); + IntPoint delta(1, 0); + greg.scrollRect(delta.x(), delta.y(), rect); + + EXPECT_TRUE(greg.hasPendingUpdate()); + PaintAggregator::PendingUpdate update; + greg.popPendingUpdate(&update); + + EXPECT_TRUE(update.paintRects.isEmpty()); + EXPECT_FALSE(update.scrollRect.isEmpty()); + + EXPECT_EQ(rect, update.scrollRect); + + EXPECT_EQ(delta.x(), update.scrollDelta.x()); + EXPECT_EQ(delta.y(), update.scrollDelta.y()); + + IntRect resultingDamage = update.calculateScrollDamage(); + IntRect expectedDamage(1, 2, 1, 4); + EXPECT_EQ(expectedDamage, resultingDamage); +} + +TEST(PaintAggregator, DoubleOverlappingScroll) +{ + PaintAggregator greg; + + IntRect rect(1, 2, 3, 4); + IntPoint delta1(1, 0); + IntPoint delta2(1, 0); + greg.scrollRect(delta1.x(), delta1.y(), rect); + greg.scrollRect(delta2.x(), delta2.y(), rect); + + EXPECT_TRUE(greg.hasPendingUpdate()); + PaintAggregator::PendingUpdate update; + greg.popPendingUpdate(&update); + + EXPECT_TRUE(update.paintRects.isEmpty()); + EXPECT_FALSE(update.scrollRect.isEmpty()); + + EXPECT_EQ(rect, update.scrollRect); + + IntPoint expectedDelta(delta1.x() + delta2.x(), + delta1.y() + delta2.y()); + EXPECT_EQ(expectedDelta.x(), update.scrollDelta.x()); + EXPECT_EQ(expectedDelta.y(), update.scrollDelta.y()); + + IntRect resultingDamage = update.calculateScrollDamage(); + IntRect expectedDamage(1, 2, 2, 4); + EXPECT_EQ(expectedDamage, resultingDamage); +} + +TEST(PaintAggregator, NegatingScroll) +{ + PaintAggregator greg; + + // Scroll twice in opposite directions by equal amounts. The result + // should be no scrolling. + + IntRect rect(1, 2, 3, 4); + IntPoint delta1(1, 0); + IntPoint delta2(-1, 0); + greg.scrollRect(delta1.x(), delta1.y(), rect); + greg.scrollRect(delta2.x(), delta2.y(), rect); + + EXPECT_FALSE(greg.hasPendingUpdate()); +} + +TEST(PaintAggregator, DiagonalScroll) +{ + PaintAggregator greg; + + // We don't support optimized diagonal scrolling, so this should result in + // repainting. + + IntRect rect(1, 2, 3, 4); + IntPoint delta(1, 1); + greg.scrollRect(delta.x(), delta.y(), rect); + + EXPECT_TRUE(greg.hasPendingUpdate()); + PaintAggregator::PendingUpdate update; + greg.popPendingUpdate(&update); + + EXPECT_TRUE(update.scrollRect.isEmpty()); + ASSERT_EQ(1U, update.paintRects.size()); + + EXPECT_EQ(rect, update.paintRects[0]); +} + +TEST(PaintAggregator, ContainedPaintAfterScroll) +{ + PaintAggregator greg; + + IntRect scrollRect(0, 0, 10, 10); + greg.scrollRect(2, 0, scrollRect); + + IntRect paintRect(4, 4, 2, 2); + greg.invalidateRect(paintRect); + + EXPECT_TRUE(greg.hasPendingUpdate()); + PaintAggregator::PendingUpdate update; + greg.popPendingUpdate(&update); + + // expecting a paint rect inside the scroll rect + EXPECT_FALSE(update.scrollRect.isEmpty()); + EXPECT_EQ(1U, update.paintRects.size()); + + EXPECT_EQ(scrollRect, update.scrollRect); + EXPECT_EQ(paintRect, update.paintRects[0]); +} + +TEST(PaintAggregator, ContainedPaintBeforeScroll) +{ + PaintAggregator greg; + + IntRect paintRect(4, 4, 2, 2); + greg.invalidateRect(paintRect); + + IntRect scrollRect(0, 0, 10, 10); + greg.scrollRect(2, 0, scrollRect); + + EXPECT_TRUE(greg.hasPendingUpdate()); + PaintAggregator::PendingUpdate update; + greg.popPendingUpdate(&update); + + // Expecting a paint rect inside the scroll rect + EXPECT_FALSE(update.scrollRect.isEmpty()); + EXPECT_EQ(1U, update.paintRects.size()); + + paintRect.move(2, 0); + + EXPECT_EQ(scrollRect, update.scrollRect); + EXPECT_EQ(paintRect, update.paintRects[0]); +} + +TEST(PaintAggregator, ContainedPaintsBeforeAndAfterScroll) +{ + PaintAggregator greg; + + IntRect paintRect1(4, 4, 2, 2); + greg.invalidateRect(paintRect1); + + IntRect scrollRect(0, 0, 10, 10); + greg.scrollRect(2, 0, scrollRect); + + IntRect paintRect2(6, 4, 2, 2); + greg.invalidateRect(paintRect2); + + IntRect expectedPaintRect = paintRect2; + + EXPECT_TRUE(greg.hasPendingUpdate()); + PaintAggregator::PendingUpdate update; + greg.popPendingUpdate(&update); + + // Expecting a paint rect inside the scroll rect + EXPECT_FALSE(update.scrollRect.isEmpty()); + EXPECT_EQ(1U, update.paintRects.size()); + + EXPECT_EQ(scrollRect, update.scrollRect); + EXPECT_EQ(expectedPaintRect, update.paintRects[0]); +} + +TEST(PaintAggregator, LargeContainedPaintAfterScroll) +{ + PaintAggregator greg; + + IntRect scrollRect(0, 0, 10, 10); + greg.scrollRect(0, 1, scrollRect); + + IntRect paintRect(0, 0, 10, 9); // Repaint 90% + greg.invalidateRect(paintRect); + + EXPECT_TRUE(greg.hasPendingUpdate()); + PaintAggregator::PendingUpdate update; + greg.popPendingUpdate(&update); + + EXPECT_TRUE(update.scrollRect.isEmpty()); + EXPECT_EQ(1U, update.paintRects.size()); + + EXPECT_EQ(scrollRect, update.paintRects[0]); +} + +TEST(PaintAggregator, LargeContainedPaintBeforeScroll) +{ + PaintAggregator greg; + + IntRect paintRect(0, 0, 10, 9); // Repaint 90% + greg.invalidateRect(paintRect); + + IntRect scrollRect(0, 0, 10, 10); + greg.scrollRect(0, 1, scrollRect); + + EXPECT_TRUE(greg.hasPendingUpdate()); + PaintAggregator::PendingUpdate update; + greg.popPendingUpdate(&update); + + EXPECT_TRUE(update.scrollRect.isEmpty()); + EXPECT_EQ(1U, update.paintRects.size()); + + EXPECT_EQ(scrollRect, update.paintRects[0]); +} + +TEST(PaintAggregator, OverlappingPaintBeforeScroll) +{ + PaintAggregator greg; + + IntRect paintRect(4, 4, 10, 2); + greg.invalidateRect(paintRect); + + IntRect scrollRect(0, 0, 10, 10); + greg.scrollRect(2, 0, scrollRect); + + IntRect expectedPaintRect = unionRect(scrollRect, paintRect); + + EXPECT_TRUE(greg.hasPendingUpdate()); + PaintAggregator::PendingUpdate update; + greg.popPendingUpdate(&update); + + EXPECT_TRUE(update.scrollRect.isEmpty()); + EXPECT_EQ(1U, update.paintRects.size()); + + EXPECT_EQ(expectedPaintRect, update.paintRects[0]); +} + +TEST(PaintAggregator, OverlappingPaintAfterScroll) +{ + PaintAggregator greg; + + IntRect scrollRect(0, 0, 10, 10); + greg.scrollRect(2, 0, scrollRect); + + IntRect paintRect(4, 4, 10, 2); + greg.invalidateRect(paintRect); + + IntRect expectedPaintRect = unionRect(scrollRect, paintRect); + + EXPECT_TRUE(greg.hasPendingUpdate()); + PaintAggregator::PendingUpdate update; + greg.popPendingUpdate(&update); + + EXPECT_TRUE(update.scrollRect.isEmpty()); + EXPECT_EQ(1U, update.paintRects.size()); + + EXPECT_EQ(expectedPaintRect, update.paintRects[0]); +} + +TEST(PaintAggregator, DisjointPaintBeforeScroll) +{ + PaintAggregator greg; + + IntRect paintRect(4, 4, 10, 2); + greg.invalidateRect(paintRect); + + IntRect scrollRect(0, 0, 2, 10); + greg.scrollRect(2, 0, scrollRect); + + EXPECT_TRUE(greg.hasPendingUpdate()); + PaintAggregator::PendingUpdate update; + greg.popPendingUpdate(&update); + + EXPECT_FALSE(update.scrollRect.isEmpty()); + EXPECT_EQ(1U, update.paintRects.size()); + + EXPECT_EQ(paintRect, update.paintRects[0]); + EXPECT_EQ(scrollRect, update.scrollRect); +} + +TEST(PaintAggregator, DisjointPaintAfterScroll) +{ + PaintAggregator greg; + + IntRect scrollRect(0, 0, 2, 10); + greg.scrollRect(2, 0, scrollRect); + + IntRect paintRect(4, 4, 10, 2); + greg.invalidateRect(paintRect); + + EXPECT_TRUE(greg.hasPendingUpdate()); + PaintAggregator::PendingUpdate update; + greg.popPendingUpdate(&update); + + EXPECT_FALSE(update.scrollRect.isEmpty()); + EXPECT_EQ(1U, update.paintRects.size()); + + EXPECT_EQ(paintRect, update.paintRects[0]); + EXPECT_EQ(scrollRect, update.scrollRect); +} + +TEST(PaintAggregator, ContainedPaintTrimmedByScroll) +{ + PaintAggregator greg; + + IntRect paintRect(4, 4, 6, 6); + greg.invalidateRect(paintRect); + + IntRect scrollRect(0, 0, 10, 10); + greg.scrollRect(2, 0, scrollRect); + + // The paint rect should have become narrower. + IntRect expectedPaintRect(6, 4, 4, 6); + + EXPECT_TRUE(greg.hasPendingUpdate()); + PaintAggregator::PendingUpdate update; + greg.popPendingUpdate(&update); + + EXPECT_FALSE(update.scrollRect.isEmpty()); + EXPECT_EQ(1U, update.paintRects.size()); + + EXPECT_EQ(expectedPaintRect, update.paintRects[0]); + EXPECT_EQ(scrollRect, update.scrollRect); +} + +TEST(PaintAggregator, ContainedPaintEliminatedByScroll) +{ + PaintAggregator greg; + + IntRect paintRect(4, 4, 6, 6); + greg.invalidateRect(paintRect); + + IntRect scrollRect(0, 0, 10, 10); + greg.scrollRect(6, 0, scrollRect); + + EXPECT_TRUE(greg.hasPendingUpdate()); + PaintAggregator::PendingUpdate update; + greg.popPendingUpdate(&update); + + EXPECT_FALSE(update.scrollRect.isEmpty()); + EXPECT_TRUE(update.paintRects.isEmpty()); + + EXPECT_EQ(scrollRect, update.scrollRect); +} + +TEST(PaintAggregator, ContainedPaintAfterScrollTrimmedByScrollDamage) +{ + PaintAggregator greg; + + IntRect scrollRect(0, 0, 10, 10); + greg.scrollRect(4, 0, scrollRect); + + IntRect paintRect(2, 0, 4, 10); + greg.invalidateRect(paintRect); + + IntRect expectedScrollDamage(0, 0, 4, 10); + IntRect expectedPaintRect(4, 0, 2, 10); + + EXPECT_TRUE(greg.hasPendingUpdate()); + PaintAggregator::PendingUpdate update; + greg.popPendingUpdate(&update); + + EXPECT_FALSE(update.scrollRect.isEmpty()); + EXPECT_EQ(1U, update.paintRects.size()); + + EXPECT_EQ(scrollRect, update.scrollRect); + EXPECT_EQ(expectedScrollDamage, update.calculateScrollDamage()); + EXPECT_EQ(expectedPaintRect, update.paintRects[0]); +} + +TEST(PaintAggregator, ContainedPaintAfterScrollEliminatedByScrollDamage) +{ + PaintAggregator greg; + + IntRect scrollRect(0, 0, 10, 10); + greg.scrollRect(4, 0, scrollRect); + + IntRect paintRect(2, 0, 2, 10); + greg.invalidateRect(paintRect); + + IntRect expectedScrollDamage(0, 0, 4, 10); + + EXPECT_TRUE(greg.hasPendingUpdate()); + PaintAggregator::PendingUpdate update; + greg.popPendingUpdate(&update); + + EXPECT_FALSE(update.scrollRect.isEmpty()); + EXPECT_TRUE(update.paintRects.isEmpty()); + + EXPECT_EQ(scrollRect, update.scrollRect); + EXPECT_EQ(expectedScrollDamage, update.calculateScrollDamage()); +} + +} // namespace -- 2.7.4