| OLD | NEW |
| (Empty) |
| 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/cpp/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 | |
| 28 PaintAggregator::PaintUpdate::PaintUpdate() : has_scroll(false) {} | |
| 29 | |
| 30 PaintAggregator::PaintUpdate::~PaintUpdate() {} | |
| 31 | |
| 32 PaintAggregator::InternalPaintUpdate::InternalPaintUpdate() {} | |
| 33 | |
| 34 PaintAggregator::InternalPaintUpdate::~InternalPaintUpdate() {} | |
| 35 | |
| 36 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 | |
| 72 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 | |
| 79 PaintAggregator::PaintAggregator() | |
| 80 : max_redundant_paint_to_scroll_area_(0.8f), | |
| 81 max_paint_rects_(10) { | |
| 82 } | |
| 83 | |
| 84 bool PaintAggregator::HasPendingUpdate() const { | |
| 85 return !update_.scroll_rect.IsEmpty() || !update_.paint_rects.empty(); | |
| 86 } | |
| 87 | |
| 88 void PaintAggregator::ClearPendingUpdate() { | |
| 89 update_ = InternalPaintUpdate(); | |
| 90 } | |
| 91 | |
| 92 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 | |
| 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 | |
| 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 | |
| 205 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 | |
| 216 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_are
a_) | |
| 237 return true; | |
| 238 | |
| 239 return false; | |
| 240 } | |
| 241 | |
| 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 | |
| 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 | |
| OLD | NEW |