• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "ppapi/utility/graphics/paint_aggregator.h"
6 
7 #include <algorithm>
8 
9 #include "ppapi/cpp/logging.h"
10 
11 // ----------------------------------------------------------------------------
12 // ALGORITHM NOTES
13 //
14 // We attempt to maintain a scroll rect in the presence of invalidations that
15 // are contained within the scroll rect.  If an invalidation crosses a scroll
16 // rect, then we just treat the scroll rect as an invalidation rect.
17 //
18 // For invalidations performed prior to scrolling and contained within the
19 // scroll rect, we offset the invalidation rects to account for the fact that
20 // the consumer will perform scrolling before painting.
21 //
22 // We only support scrolling along one axis at a time.  A diagonal scroll will
23 // therefore be treated as an invalidation.
24 // ----------------------------------------------------------------------------
25 
26 namespace pp {
27 
PaintUpdate()28 PaintAggregator::PaintUpdate::PaintUpdate() : has_scroll(false) {}
29 
~PaintUpdate()30 PaintAggregator::PaintUpdate::~PaintUpdate() {}
31 
InternalPaintUpdate()32 PaintAggregator::InternalPaintUpdate::InternalPaintUpdate() {}
33 
~InternalPaintUpdate()34 PaintAggregator::InternalPaintUpdate::~InternalPaintUpdate() {}
35 
GetScrollDamage() const36 Rect PaintAggregator::InternalPaintUpdate::GetScrollDamage() const {
37   // Should only be scrolling in one direction at a time.
38   PP_DCHECK(!(scroll_delta.x() && scroll_delta.y()));
39 
40   Rect damaged_rect;
41 
42   // Compute the region we will expose by scrolling, and paint that into a
43   // shared memory section.
44   if (scroll_delta.x()) {
45     int32_t dx = scroll_delta.x();
46     damaged_rect.set_y(scroll_rect.y());
47     damaged_rect.set_height(scroll_rect.height());
48     if (dx > 0) {
49       damaged_rect.set_x(scroll_rect.x());
50       damaged_rect.set_width(dx);
51     } else {
52       damaged_rect.set_x(scroll_rect.right() + dx);
53       damaged_rect.set_width(-dx);
54     }
55   } else {
56     int32_t dy = scroll_delta.y();
57     damaged_rect.set_x(scroll_rect.x());
58     damaged_rect.set_width(scroll_rect.width());
59     if (dy > 0) {
60       damaged_rect.set_y(scroll_rect.y());
61       damaged_rect.set_height(dy);
62     } else {
63       damaged_rect.set_y(scroll_rect.bottom() + dy);
64       damaged_rect.set_height(-dy);
65     }
66   }
67 
68   // In case the scroll offset exceeds the width/height of the scroll rect
69   return scroll_rect.Intersect(damaged_rect);
70 }
71 
GetPaintBounds() const72 Rect PaintAggregator::InternalPaintUpdate::GetPaintBounds() const {
73   Rect bounds;
74   for (size_t i = 0; i < paint_rects.size(); ++i)
75     bounds = bounds.Union(paint_rects[i]);
76   return bounds;
77 }
78 
PaintAggregator()79 PaintAggregator::PaintAggregator()
80     : max_redundant_paint_to_scroll_area_(0.8f),
81       max_paint_rects_(10) {
82 }
83 
HasPendingUpdate() const84 bool PaintAggregator::HasPendingUpdate() const {
85   return !update_.scroll_rect.IsEmpty() || !update_.paint_rects.empty();
86 }
87 
ClearPendingUpdate()88 void PaintAggregator::ClearPendingUpdate() {
89   update_ = InternalPaintUpdate();
90 }
91 
GetPendingUpdate() const92 PaintAggregator::PaintUpdate PaintAggregator::GetPendingUpdate() const {
93   // Convert the internal paint update to the external one, which includes a
94   // bit more precomputed info for the caller.
95   PaintUpdate ret;
96   ret.scroll_delta = update_.scroll_delta;
97   ret.scroll_rect = update_.scroll_rect;
98   ret.has_scroll = ret.scroll_delta.x() != 0 || ret.scroll_delta.y() != 0;
99 
100   ret.paint_rects.reserve(update_.paint_rects.size() + 1);
101   for (size_t i = 0; i < update_.paint_rects.size(); i++)
102     ret.paint_rects.push_back(update_.paint_rects[i]);
103 
104   ret.paint_bounds = update_.GetPaintBounds();
105 
106   // Also include the scroll damage (if any) in the paint rects.
107   if (ret.has_scroll) {
108     PP_Rect scroll_damage = update_.GetScrollDamage();
109     ret.paint_rects.push_back(scroll_damage);
110     ret.paint_bounds = ret.paint_bounds.Union(scroll_damage);
111   }
112 
113   return ret;
114 }
115 
InvalidateRect(const Rect & rect)116 void PaintAggregator::InvalidateRect(const Rect& rect) {
117   // Combine overlapping paints using smallest bounding box.
118   for (size_t i = 0; i < update_.paint_rects.size(); ++i) {
119     const Rect& existing_rect = update_.paint_rects[i];
120     if (existing_rect.Contains(rect))  // Optimize out redundancy.
121       return;
122     if (rect.Intersects(existing_rect) || rect.SharesEdgeWith(existing_rect)) {
123       // Re-invalidate in case the union intersects other paint rects.
124       Rect combined_rect = existing_rect.Union(rect);
125       update_.paint_rects.erase(update_.paint_rects.begin() + i);
126       InvalidateRect(combined_rect);
127       return;
128     }
129   }
130 
131   // Add a non-overlapping paint.
132   update_.paint_rects.push_back(rect);
133 
134   // If the new paint overlaps with a scroll, then it forces an invalidation of
135   // the scroll.  If the new paint is contained by a scroll, then trim off the
136   // scroll damage to avoid redundant painting.
137   if (!update_.scroll_rect.IsEmpty()) {
138     if (ShouldInvalidateScrollRect(rect)) {
139       InvalidateScrollRect();
140     } else if (update_.scroll_rect.Contains(rect)) {
141       update_.paint_rects[update_.paint_rects.size() - 1] =
142           rect.Subtract(update_.GetScrollDamage());
143       if (update_.paint_rects[update_.paint_rects.size() - 1].IsEmpty())
144         update_.paint_rects.erase(update_.paint_rects.end() - 1);
145     }
146   }
147 
148   if (update_.paint_rects.size() > max_paint_rects_)
149     CombinePaintRects();
150 }
151 
ScrollRect(const Rect & clip_rect,const Point & amount)152 void PaintAggregator::ScrollRect(const Rect& clip_rect, const Point& amount) {
153   // We only support scrolling along one axis at a time.
154   if (amount.x() != 0 && amount.y() != 0) {
155     InvalidateRect(clip_rect);
156     return;
157   }
158 
159   // We can only scroll one rect at a time.
160   if (!update_.scroll_rect.IsEmpty() && update_.scroll_rect != clip_rect) {
161     InvalidateRect(clip_rect);
162     return;
163   }
164 
165   // Again, we only support scrolling along one axis at a time.  Make sure this
166   // update doesn't scroll on a different axis than any existing one.
167   if ((amount.x() && update_.scroll_delta.y()) ||
168       (amount.y() && update_.scroll_delta.x())) {
169     InvalidateRect(clip_rect);
170     return;
171   }
172 
173   // The scroll rect is new or isn't changing (though the scroll amount may
174   // be changing).
175   update_.scroll_rect = clip_rect;
176   update_.scroll_delta += amount;
177 
178   // We might have just wiped out a pre-existing scroll.
179   if (update_.scroll_delta == Point()) {
180     update_.scroll_rect = Rect();
181     return;
182   }
183 
184   // Adjust any contained paint rects and check for any overlapping paints.
185   for (size_t i = 0; i < update_.paint_rects.size(); ++i) {
186     if (update_.scroll_rect.Contains(update_.paint_rects[i])) {
187       update_.paint_rects[i] = ScrollPaintRect(update_.paint_rects[i], amount);
188       // The rect may have been scrolled out of view.
189       if (update_.paint_rects[i].IsEmpty()) {
190         update_.paint_rects.erase(update_.paint_rects.begin() + i);
191         i--;
192       }
193     } else if (update_.scroll_rect.Intersects(update_.paint_rects[i])) {
194       InvalidateScrollRect();
195       return;
196     }
197   }
198 
199   // If the new scroll overlaps too much with contained paint rects, then force
200   // an invalidation of the scroll.
201   if (ShouldInvalidateScrollRect(Rect()))
202     InvalidateScrollRect();
203 }
204 
ScrollPaintRect(const Rect & paint_rect,const Point & amount) const205 Rect PaintAggregator::ScrollPaintRect(const Rect& paint_rect,
206                                       const Point& amount) const {
207   Rect result = paint_rect;
208 
209   result.Offset(amount);
210   result = update_.scroll_rect.Intersect(result);
211 
212   // Subtract out the scroll damage rect to avoid redundant painting.
213   return result.Subtract(update_.GetScrollDamage());
214 }
215 
ShouldInvalidateScrollRect(const Rect & rect) const216 bool PaintAggregator::ShouldInvalidateScrollRect(const Rect& rect) const {
217   if (!rect.IsEmpty()) {
218     if (!update_.scroll_rect.Intersects(rect))
219       return false;
220 
221     if (!update_.scroll_rect.Contains(rect))
222       return true;
223   }
224 
225   // Check if the combined area of all contained paint rects plus this new
226   // rect comes too close to the area of the scroll_rect.  If so, then we
227   // might as well invalidate the scroll rect.
228 
229   int paint_area = rect.size().GetArea();
230   for (size_t i = 0; i < update_.paint_rects.size(); ++i) {
231     const Rect& existing_rect = update_.paint_rects[i];
232     if (update_.scroll_rect.Contains(existing_rect))
233       paint_area += existing_rect.size().GetArea();
234   }
235   int scroll_area = update_.scroll_rect.size().GetArea();
236   if (float(paint_area) / float(scroll_area) > max_redundant_paint_to_scroll_area_)
237     return true;
238 
239   return false;
240 }
241 
InvalidateScrollRect()242 void PaintAggregator::InvalidateScrollRect() {
243   Rect scroll_rect = update_.scroll_rect;
244   update_.scroll_rect = Rect();
245   update_.scroll_delta = Point();
246   InvalidateRect(scroll_rect);
247 }
248 
CombinePaintRects()249 void PaintAggregator::CombinePaintRects() {
250   // Combine paint rects down to at most two rects: one inside the scroll_rect
251   // and one outside the scroll_rect.  If there is no scroll_rect, then just
252   // use the smallest bounding box for all paint rects.
253   //
254   // NOTE: This is a fairly simple algorithm.  We could get fancier by only
255   // combining two rects to get us under the max_paint_rects limit, but if we
256   // reach this method then it means we're hitting a rare case, so there's no
257   // need to over-optimize it.
258   //
259   if (update_.scroll_rect.IsEmpty()) {
260     Rect bounds = update_.GetPaintBounds();
261     update_.paint_rects.clear();
262     update_.paint_rects.push_back(bounds);
263   } else {
264     Rect inner, outer;
265     for (size_t i = 0; i < update_.paint_rects.size(); ++i) {
266       const Rect& existing_rect = update_.paint_rects[i];
267       if (update_.scroll_rect.Contains(existing_rect)) {
268         inner = inner.Union(existing_rect);
269       } else {
270         outer = outer.Union(existing_rect);
271       }
272     }
273     update_.paint_rects.clear();
274     update_.paint_rects.push_back(inner);
275     update_.paint_rects.push_back(outer);
276   }
277 }
278 
279 }  // namespace pp
280