1 /*
2 * Copyright (C) 2011 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 #include "config.h"
32 #include "web/painting/PaintAggregator.h"
33
34 #include "public/platform/Platform.h"
35
36 using namespace blink;
37
38 namespace blink {
39
40 // ----------------------------------------------------------------------------
41 // ALGORITHM NOTES
42 //
43 // We attempt to maintain a scroll rect in the presence of invalidations that
44 // are contained within the scroll rect. If an invalidation crosses a scroll
45 // rect, then we just treat the scroll rect as an invalidation rect.
46 //
47 // For invalidations performed prior to scrolling and contained within the
48 // scroll rect, we offset the invalidation rects to account for the fact that
49 // the consumer will perform scrolling before painting.
50 //
51 // We only support scrolling along one axis at a time. A diagonal scroll will
52 // therefore be treated as an invalidation.
53 // ----------------------------------------------------------------------------
54
55 // If the combined area of paint rects contained within the scroll rect grows
56 // too large, then we might as well just treat the scroll rect as a paint rect.
57 // This constant sets the max ratio of paint rect area to scroll rect area that
58 // we will tolerate before dograding the scroll into a repaint.
59 static const float maxRedundantPaintToScrollArea = 0.8f;
60
61 // The maximum number of paint rects. If we exceed this limit, then we'll
62 // start combining paint rects (see CombinePaintRects). This limiting is
63 // important since the WebKit code associated with deciding what to paint given
64 // a paint rect can be significant.
65 static const size_t maxPaintRects = 5;
66
67 // If the combined area of paint rects divided by the area of the union of all
68 // paint rects exceeds this threshold, then we will combine the paint rects.
69 static const float maxPaintRectsAreaRatio = 0.7f;
70
calculateArea(const IntRect & rect)71 static int calculateArea(const IntRect& rect)
72 {
73 return rect.size().width() * rect.size().height();
74 }
75
76 // Subtracts out the intersection of |a| and |b| from |a|, assuming |b| fully
77 // overlaps with |a| in either the x- or y-direction. If there is no full
78 // overlap, then |a| is returned.
subtractIntersection(const IntRect & a,const IntRect & b)79 static IntRect subtractIntersection(const IntRect& a, const IntRect& b)
80 {
81 // boundary cases:
82 if (!a.intersects(b))
83 return a;
84 if (b.contains(a))
85 return IntRect();
86
87 int rx = a.x();
88 int ry = a.y();
89 int rr = a.maxX();
90 int rb = a.maxY();
91
92 if (b.y() <= a.y() && b.maxY() >= a.maxY()) {
93 // complete intersection in the y-direction
94 if (b.x() <= a.x())
95 rx = b.maxX();
96 else
97 rr = b.x();
98 } else if (b.x() <= a.x() && b.maxX() >= a.maxX()) {
99 // complete intersection in the x-direction
100 if (b.y() <= a.y())
101 ry = b.maxY();
102 else
103 rb = b.y();
104 }
105 return IntRect(rx, ry, rr - rx, rb - ry);
106 }
107
108 // Returns true if |a| and |b| share an entire edge (i.e., same width or same
109 // height), and the rectangles do not overlap.
sharesEdge(const IntRect & a,const IntRect & b)110 static bool sharesEdge(const IntRect& a, const IntRect& b)
111 {
112 return (a.y() == b.y() && a.height() == b.height() && (a.x() == b.maxX() || a.maxX() == b.x()))
113 || (a.x() == b.x() && a.width() == b.width() && (a.y() == b.maxY() || a.maxY() == b.y()));
114 }
115
PendingUpdate()116 PaintAggregator::PendingUpdate::PendingUpdate()
117 {
118 }
119
~PendingUpdate()120 PaintAggregator::PendingUpdate::~PendingUpdate()
121 {
122 }
123
calculateScrollDamage() const124 IntRect PaintAggregator::PendingUpdate::calculateScrollDamage() const
125 {
126 // Should only be scrolling in one direction at a time.
127 ASSERT(!(scrollDelta.x() && scrollDelta.y()));
128
129 IntRect damagedRect;
130
131 // Compute the region we will expose by scrolling, and paint that into a
132 // shared memory section.
133 if (scrollDelta.x()) {
134 int dx = scrollDelta.x();
135 damagedRect.setY(scrollRect.y());
136 damagedRect.setHeight(scrollRect.height());
137 if (dx > 0) {
138 damagedRect.setX(scrollRect.x());
139 damagedRect.setWidth(dx);
140 } else {
141 damagedRect.setX(scrollRect.maxX() + dx);
142 damagedRect.setWidth(-dx);
143 }
144 } else {
145 int dy = scrollDelta.y();
146 damagedRect.setX(scrollRect.x());
147 damagedRect.setWidth(scrollRect.width());
148 if (dy > 0) {
149 damagedRect.setY(scrollRect.y());
150 damagedRect.setHeight(dy);
151 } else {
152 damagedRect.setY(scrollRect.maxY() + dy);
153 damagedRect.setHeight(-dy);
154 }
155 }
156
157 // In case the scroll offset exceeds the width/height of the scroll rect
158 return intersection(scrollRect, damagedRect);
159 }
160
calculatePaintBounds() const161 IntRect PaintAggregator::PendingUpdate::calculatePaintBounds() const
162 {
163 IntRect bounds;
164 for (size_t i = 0; i < paintRects.size(); ++i)
165 bounds.unite(paintRects[i]);
166 return bounds;
167 }
168
hasPendingUpdate() const169 bool PaintAggregator::hasPendingUpdate() const
170 {
171 return !m_update.scrollRect.isEmpty() || !m_update.paintRects.isEmpty();
172 }
173
clearPendingUpdate()174 void PaintAggregator::clearPendingUpdate()
175 {
176 m_update = PendingUpdate();
177 }
178
popPendingUpdate(PendingUpdate * update)179 void PaintAggregator::popPendingUpdate(PendingUpdate* update)
180 {
181 // Combine paint rects if their combined area is not sufficiently less than
182 // the area of the union of all paint rects. We skip this if there is a
183 // scroll rect since scrolling benefits from smaller paint rects.
184 if (m_update.scrollRect.isEmpty() && m_update.paintRects.size() > 1) {
185 int paintArea = 0;
186 IntRect unionRect;
187 for (size_t i = 0; i < m_update.paintRects.size(); ++i) {
188 paintArea += calculateArea(m_update.paintRects[i]);
189 unionRect.unite(m_update.paintRects[i]);
190 }
191 int unionArea = calculateArea(unionRect);
192 if (float(paintArea) / float(unionArea) > maxPaintRectsAreaRatio)
193 combinePaintRects();
194 }
195 *update = m_update;
196 clearPendingUpdate();
197 }
198
invalidateRect(const IntRect & rect)199 void PaintAggregator::invalidateRect(const IntRect& rect)
200 {
201 // Combine overlapping paints using smallest bounding box.
202 for (size_t i = 0; i < m_update.paintRects.size(); ++i) {
203 const IntRect& existingRect = m_update.paintRects[i];
204 if (existingRect.contains(rect)) // Optimize out redundancy.
205 return;
206 if (rect.intersects(existingRect) || sharesEdge(rect, existingRect)) {
207 // Re-invalidate in case the union intersects other paint rects.
208 IntRect combinedRect = unionRect(existingRect, rect);
209 m_update.paintRects.remove(i);
210 invalidateRect(combinedRect);
211 return;
212 }
213 }
214
215 // Add a non-overlapping paint.
216 m_update.paintRects.append(rect);
217
218 // If the new paint overlaps with a scroll, then it forces an invalidation of
219 // the scroll. If the new paint is contained by a scroll, then trim off the
220 // scroll damage to avoid redundant painting.
221 if (!m_update.scrollRect.isEmpty()) {
222 if (shouldInvalidateScrollRect(rect))
223 invalidateScrollRect();
224 else if (m_update.scrollRect.contains(rect)) {
225 m_update.paintRects[m_update.paintRects.size() - 1] =
226 subtractIntersection(rect, m_update.calculateScrollDamage());
227 if (m_update.paintRects[m_update.paintRects.size() - 1].isEmpty())
228 m_update.paintRects.remove(m_update.paintRects.size() - 1);
229 }
230 }
231
232 if (m_update.paintRects.size() > maxPaintRects)
233 combinePaintRects();
234
235 // Track how large the paintRects vector grows during an invalidation
236 // sequence. Note: A subsequent invalidation may end up being combined
237 // with all existing paints, which means that tracking the size of
238 // paintRects at the time when popPendingUpdate() is called may mask
239 // certain performance problems.
240 blink::Platform::current()->histogramCustomCounts("MPArch.RW_IntermediatePaintRectCount",
241 m_update.paintRects.size(), 1, 100, 50);
242 }
243
scrollRect(int dx,int dy,const IntRect & clipRect)244 void PaintAggregator::scrollRect(int dx, int dy, const IntRect& clipRect)
245 {
246 // We only support scrolling along one axis at a time.
247 if (dx && dy) {
248 invalidateRect(clipRect);
249 return;
250 }
251
252 // We can only scroll one rect at a time.
253 if (!m_update.scrollRect.isEmpty() && m_update.scrollRect != clipRect) {
254 invalidateRect(clipRect);
255 return;
256 }
257
258 // Again, we only support scrolling along one axis at a time. Make sure this
259 // update doesn't scroll on a different axis than any existing one.
260 if ((dx && m_update.scrollDelta.y()) || (dy && m_update.scrollDelta.x())) {
261 invalidateRect(clipRect);
262 return;
263 }
264
265 // The scroll rect is new or isn't changing (though the scroll amount may
266 // be changing).
267 m_update.scrollRect = clipRect;
268 m_update.scrollDelta.move(dx, dy);
269
270 // We might have just wiped out a pre-existing scroll.
271 if (m_update.scrollDelta == IntPoint()) {
272 m_update.scrollRect = IntRect();
273 return;
274 }
275
276 // Adjust any contained paint rects and check for any overlapping paints.
277 for (size_t i = 0; i < m_update.paintRects.size(); ++i) {
278 if (m_update.scrollRect.contains(m_update.paintRects[i])) {
279 m_update.paintRects[i] = scrollPaintRect(m_update.paintRects[i], dx, dy);
280 // The rect may have been scrolled out of view.
281 if (m_update.paintRects[i].isEmpty()) {
282 m_update.paintRects.remove(i);
283 i--;
284 }
285 } else if (m_update.scrollRect.intersects(m_update.paintRects[i])) {
286 invalidateScrollRect();
287 return;
288 }
289 }
290
291 // If the new scroll overlaps too much with contained paint rects, then force
292 // an invalidation of the scroll.
293 if (shouldInvalidateScrollRect(IntRect()))
294 invalidateScrollRect();
295 }
296
scrollPaintRect(const IntRect & paintRect,int dx,int dy) const297 IntRect PaintAggregator::scrollPaintRect(const IntRect& paintRect, int dx, int dy) const
298 {
299 IntRect result = paintRect;
300
301 result.move(dx, dy);
302 result = intersection(m_update.scrollRect, result);
303
304 // Subtract out the scroll damage rect to avoid redundant painting.
305 return subtractIntersection(result, m_update.calculateScrollDamage());
306 }
307
shouldInvalidateScrollRect(const IntRect & rect) const308 bool PaintAggregator::shouldInvalidateScrollRect(const IntRect& rect) const
309 {
310 if (!rect.isEmpty()) {
311 if (!m_update.scrollRect.intersects(rect))
312 return false;
313
314 if (!m_update.scrollRect.contains(rect))
315 return true;
316 }
317
318 // Check if the combined area of all contained paint rects plus this new
319 // rect comes too close to the area of the scrollRect. If so, then we
320 // might as well invalidate the scroll rect.
321
322 int paintArea = calculateArea(rect);
323 for (size_t i = 0; i < m_update.paintRects.size(); ++i) {
324 const IntRect& existingRect = m_update.paintRects[i];
325 if (m_update.scrollRect.contains(existingRect))
326 paintArea += calculateArea(existingRect);
327 }
328 int scrollArea = calculateArea(m_update.scrollRect);
329 if (float(paintArea) / float(scrollArea) > maxRedundantPaintToScrollArea)
330 return true;
331
332 return false;
333 }
334
invalidateScrollRect()335 void PaintAggregator::invalidateScrollRect()
336 {
337 IntRect scrollRect = m_update.scrollRect;
338 m_update.scrollRect = IntRect();
339 m_update.scrollDelta = IntPoint();
340 invalidateRect(scrollRect);
341 }
342
combinePaintRects()343 void PaintAggregator::combinePaintRects()
344 {
345 // Combine paint rects do to at most two rects: one inside the scrollRect
346 // and one outside the scrollRect. If there is no scrollRect, then just
347 // use the smallest bounding box for all paint rects.
348 //
349 // NOTE: This is a fairly simple algorithm. We could get fancier by only
350 // combining two rects to get us under the maxPaintRects limit, but if we
351 // reach this method then it means we're hitting a rare case, so there's no
352 // need to over-optimize it.
353 //
354 if (m_update.scrollRect.isEmpty()) {
355 IntRect bounds = m_update.calculatePaintBounds();
356 m_update.paintRects.clear();
357 m_update.paintRects.append(bounds);
358 } else {
359 IntRect inner, outer;
360 for (size_t i = 0; i < m_update.paintRects.size(); ++i) {
361 const IntRect& existingRect = m_update.paintRects[i];
362 if (m_update.scrollRect.contains(existingRect))
363 inner.unite(existingRect);
364 else
365 outer.unite(existingRect);
366 }
367 m_update.paintRects.clear();
368 m_update.paintRects.append(inner);
369 m_update.paintRects.append(outer);
370 }
371 }
372
373 } // namespace blink
374