| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2009 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 "chrome/renderer/paint_aggregator.h" | |
| 6 | |
| 7 #include "base/logging.h" | |
| 8 #include "base/metrics/histogram.h" | |
| 9 | |
| 10 // ---------------------------------------------------------------------------- | |
| 11 // ALGORITHM NOTES | |
| 12 // | |
| 13 // We attempt to maintain a scroll rect in the presence of invalidations that | |
| 14 // are contained within the scroll rect. If an invalidation crosses a scroll | |
| 15 // rect, then we just treat the scroll rect as an invalidation rect. | |
| 16 // | |
| 17 // For invalidations performed prior to scrolling and contained within the | |
| 18 // scroll rect, we offset the invalidation rects to account for the fact that | |
| 19 // the consumer will perform scrolling before painting. | |
| 20 // | |
| 21 // We only support scrolling along one axis at a time. A diagonal scroll will | |
| 22 // therefore be treated as an invalidation. | |
| 23 // ---------------------------------------------------------------------------- | |
| 24 | |
| 25 // If the combined area of paint rects contained within the scroll rect grows | |
| 26 // too large, then we might as well just treat the scroll rect as a paint rect. | |
| 27 // This constant sets the max ratio of paint rect area to scroll rect area that | |
| 28 // we will tolerate before dograding the scroll into a repaint. | |
| 29 static const float kMaxRedundantPaintToScrollArea = 0.8f; | |
| 30 | |
| 31 // The maximum number of paint rects. If we exceed this limit, then we'll | |
| 32 // start combining paint rects (see CombinePaintRects). This limiting is | |
| 33 // important since the WebKit code associated with deciding what to paint given | |
| 34 // a paint rect can be significant. | |
| 35 static const size_t kMaxPaintRects = 5; | |
| 36 | |
| 37 // If the combined area of paint rects divided by the area of the union of all | |
| 38 // paint rects exceeds this threshold, then we will combine the paint rects. | |
| 39 static const float kMaxPaintRectsAreaRatio = 0.7f; | |
| 40 | |
| 41 PaintAggregator::PendingUpdate::PendingUpdate() {} | |
| 42 | |
| 43 PaintAggregator::PendingUpdate::~PendingUpdate() {} | |
| 44 | |
| 45 gfx::Rect PaintAggregator::PendingUpdate::GetScrollDamage() const { | |
| 46 // Should only be scrolling in one direction at a time. | |
| 47 DCHECK(!(scroll_delta.x() && scroll_delta.y())); | |
| 48 | |
| 49 gfx::Rect damaged_rect; | |
| 50 | |
| 51 // Compute the region we will expose by scrolling, and paint that into a | |
| 52 // shared memory section. | |
| 53 if (scroll_delta.x()) { | |
| 54 int dx = scroll_delta.x(); | |
| 55 damaged_rect.set_y(scroll_rect.y()); | |
| 56 damaged_rect.set_height(scroll_rect.height()); | |
| 57 if (dx > 0) { | |
| 58 damaged_rect.set_x(scroll_rect.x()); | |
| 59 damaged_rect.set_width(dx); | |
| 60 } else { | |
| 61 damaged_rect.set_x(scroll_rect.right() + dx); | |
| 62 damaged_rect.set_width(-dx); | |
| 63 } | |
| 64 } else { | |
| 65 int dy = scroll_delta.y(); | |
| 66 damaged_rect.set_x(scroll_rect.x()); | |
| 67 damaged_rect.set_width(scroll_rect.width()); | |
| 68 if (dy > 0) { | |
| 69 damaged_rect.set_y(scroll_rect.y()); | |
| 70 damaged_rect.set_height(dy); | |
| 71 } else { | |
| 72 damaged_rect.set_y(scroll_rect.bottom() + dy); | |
| 73 damaged_rect.set_height(-dy); | |
| 74 } | |
| 75 } | |
| 76 | |
| 77 // In case the scroll offset exceeds the width/height of the scroll rect | |
| 78 return scroll_rect.Intersect(damaged_rect); | |
| 79 } | |
| 80 | |
| 81 gfx::Rect PaintAggregator::PendingUpdate::GetPaintBounds() const { | |
| 82 gfx::Rect bounds; | |
| 83 for (size_t i = 0; i < paint_rects.size(); ++i) | |
| 84 bounds = bounds.Union(paint_rects[i]); | |
| 85 return bounds; | |
| 86 } | |
| 87 | |
| 88 bool PaintAggregator::HasPendingUpdate() const { | |
| 89 return !update_.scroll_rect.IsEmpty() || !update_.paint_rects.empty(); | |
| 90 } | |
| 91 | |
| 92 void PaintAggregator::ClearPendingUpdate() { | |
| 93 update_ = PendingUpdate(); | |
| 94 } | |
| 95 | |
| 96 void PaintAggregator::PopPendingUpdate(PendingUpdate* update) { | |
| 97 // Combine paint rects if their combined area is not sufficiently less than | |
| 98 // the area of the union of all paint rects. We skip this if there is a | |
| 99 // scroll rect since scrolling benefits from smaller paint rects. | |
| 100 if (update_.scroll_rect.IsEmpty() && update_.paint_rects.size() > 1) { | |
| 101 int paint_area = 0; | |
| 102 gfx::Rect union_rect; | |
| 103 for (size_t i = 0; i < update_.paint_rects.size(); ++i) { | |
| 104 paint_area += update_.paint_rects[i].size().GetArea(); | |
| 105 union_rect = union_rect.Union(update_.paint_rects[i]); | |
| 106 } | |
| 107 int union_area = union_rect.size().GetArea(); | |
| 108 if (float(paint_area) / float(union_area) > kMaxPaintRectsAreaRatio) | |
| 109 CombinePaintRects(); | |
| 110 } | |
| 111 *update = update_; | |
| 112 ClearPendingUpdate(); | |
| 113 } | |
| 114 | |
| 115 void PaintAggregator::InvalidateRect(const gfx::Rect& rect) { | |
| 116 // Combine overlapping paints using smallest bounding box. | |
| 117 for (size_t i = 0; i < update_.paint_rects.size(); ++i) { | |
| 118 const gfx::Rect& existing_rect = update_.paint_rects[i]; | |
| 119 if (existing_rect.Contains(rect)) // Optimize out redundancy. | |
| 120 return; | |
| 121 if (rect.Intersects(existing_rect) || rect.SharesEdgeWith(existing_rect)) { | |
| 122 // Re-invalidate in case the union intersects other paint rects. | |
| 123 gfx::Rect combined_rect = existing_rect.Union(rect); | |
| 124 update_.paint_rects.erase(update_.paint_rects.begin() + i); | |
| 125 InvalidateRect(combined_rect); | |
| 126 return; | |
| 127 } | |
| 128 } | |
| 129 | |
| 130 // Add a non-overlapping paint. | |
| 131 update_.paint_rects.push_back(rect); | |
| 132 | |
| 133 // If the new paint overlaps with a scroll, then it forces an invalidation of | |
| 134 // the scroll. If the new paint is contained by a scroll, then trim off the | |
| 135 // scroll damage to avoid redundant painting. | |
| 136 if (!update_.scroll_rect.IsEmpty()) { | |
| 137 if (ShouldInvalidateScrollRect(rect)) { | |
| 138 InvalidateScrollRect(); | |
| 139 } else if (update_.scroll_rect.Contains(rect)) { | |
| 140 update_.paint_rects[update_.paint_rects.size() - 1] = | |
| 141 rect.Subtract(update_.GetScrollDamage()); | |
| 142 if (update_.paint_rects[update_.paint_rects.size() - 1].IsEmpty()) | |
| 143 update_.paint_rects.erase(update_.paint_rects.end() - 1); | |
| 144 } | |
| 145 } | |
| 146 | |
| 147 if (update_.paint_rects.size() > kMaxPaintRects) | |
| 148 CombinePaintRects(); | |
| 149 | |
| 150 // Track how large the paint_rects vector grows during an invalidation | |
| 151 // sequence. Note: A subsequent invalidation may end up being combined | |
| 152 // with all existing paints, which means that tracking the size of | |
| 153 // paint_rects at the time when PopPendingUpdate() is called may mask | |
| 154 // certain performance problems. | |
| 155 HISTOGRAM_COUNTS_100("MPArch.RW_IntermediatePaintRectCount", | |
| 156 update_.paint_rects.size()); | |
| 157 } | |
| 158 | |
| 159 void PaintAggregator::ScrollRect(int dx, int dy, const gfx::Rect& clip_rect) { | |
| 160 // We only support scrolling along one axis at a time. | |
| 161 if (dx != 0 && dy != 0) { | |
| 162 InvalidateRect(clip_rect); | |
| 163 return; | |
| 164 } | |
| 165 | |
| 166 // We can only scroll one rect at a time. | |
| 167 if (!update_.scroll_rect.IsEmpty() && | |
| 168 !update_.scroll_rect.Equals(clip_rect)) { | |
| 169 InvalidateRect(clip_rect); | |
| 170 return; | |
| 171 } | |
| 172 | |
| 173 // Again, we only support scrolling along one axis at a time. Make sure this | |
| 174 // update doesn't scroll on a different axis than any existing one. | |
| 175 if ((dx && update_.scroll_delta.y()) || (dy && update_.scroll_delta.x())) { | |
| 176 InvalidateRect(clip_rect); | |
| 177 return; | |
| 178 } | |
| 179 | |
| 180 // The scroll rect is new or isn't changing (though the scroll amount may | |
| 181 // be changing). | |
| 182 update_.scroll_rect = clip_rect; | |
| 183 update_.scroll_delta.Offset(dx, dy); | |
| 184 | |
| 185 // We might have just wiped out a pre-existing scroll. | |
| 186 if (update_.scroll_delta == gfx::Point()) { | |
| 187 update_.scroll_rect = gfx::Rect(); | |
| 188 return; | |
| 189 } | |
| 190 | |
| 191 // Adjust any contained paint rects and check for any overlapping paints. | |
| 192 for (size_t i = 0; i < update_.paint_rects.size(); ++i) { | |
| 193 if (update_.scroll_rect.Contains(update_.paint_rects[i])) { | |
| 194 update_.paint_rects[i] = ScrollPaintRect(update_.paint_rects[i], dx, dy); | |
| 195 // The rect may have been scrolled out of view. | |
| 196 if (update_.paint_rects[i].IsEmpty()) { | |
| 197 update_.paint_rects.erase(update_.paint_rects.begin() + i); | |
| 198 i--; | |
| 199 } | |
| 200 } else if (update_.scroll_rect.Intersects(update_.paint_rects[i])) { | |
| 201 InvalidateScrollRect(); | |
| 202 return; | |
| 203 } | |
| 204 } | |
| 205 | |
| 206 // If the new scroll overlaps too much with contained paint rects, then force | |
| 207 // an invalidation of the scroll. | |
| 208 if (ShouldInvalidateScrollRect(gfx::Rect())) | |
| 209 InvalidateScrollRect(); | |
| 210 } | |
| 211 | |
| 212 gfx::Rect PaintAggregator::ScrollPaintRect(const gfx::Rect& paint_rect, | |
| 213 int dx, int dy) const { | |
| 214 gfx::Rect result = paint_rect; | |
| 215 | |
| 216 result.Offset(dx, dy); | |
| 217 result = update_.scroll_rect.Intersect(result); | |
| 218 | |
| 219 // Subtract out the scroll damage rect to avoid redundant painting. | |
| 220 return result.Subtract(update_.GetScrollDamage()); | |
| 221 } | |
| 222 | |
| 223 bool PaintAggregator::ShouldInvalidateScrollRect(const gfx::Rect& rect) const { | |
| 224 if (!rect.IsEmpty()) { | |
| 225 if (!update_.scroll_rect.Intersects(rect)) | |
| 226 return false; | |
| 227 | |
| 228 if (!update_.scroll_rect.Contains(rect)) | |
| 229 return true; | |
| 230 } | |
| 231 | |
| 232 // Check if the combined area of all contained paint rects plus this new | |
| 233 // rect comes too close to the area of the scroll_rect. If so, then we | |
| 234 // might as well invalidate the scroll rect. | |
| 235 | |
| 236 int paint_area = rect.size().GetArea(); | |
| 237 for (size_t i = 0; i < update_.paint_rects.size(); ++i) { | |
| 238 const gfx::Rect& existing_rect = update_.paint_rects[i]; | |
| 239 if (update_.scroll_rect.Contains(existing_rect)) | |
| 240 paint_area += existing_rect.size().GetArea(); | |
| 241 } | |
| 242 int scroll_area = update_.scroll_rect.size().GetArea(); | |
| 243 if (float(paint_area) / float(scroll_area) > kMaxRedundantPaintToScrollArea) | |
| 244 return true; | |
| 245 | |
| 246 return false; | |
| 247 } | |
| 248 | |
| 249 void PaintAggregator::InvalidateScrollRect() { | |
| 250 gfx::Rect scroll_rect = update_.scroll_rect; | |
| 251 update_.scroll_rect = gfx::Rect(); | |
| 252 update_.scroll_delta = gfx::Point(); | |
| 253 InvalidateRect(scroll_rect); | |
| 254 } | |
| 255 | |
| 256 void PaintAggregator::CombinePaintRects() { | |
| 257 // Combine paint rects do to at most two rects: one inside the scroll_rect | |
| 258 // and one outside the scroll_rect. If there is no scroll_rect, then just | |
| 259 // use the smallest bounding box for all paint rects. | |
| 260 // | |
| 261 // NOTE: This is a fairly simple algorithm. We could get fancier by only | |
| 262 // combining two rects to get us under the kMaxPaintRects limit, but if we | |
| 263 // reach this method then it means we're hitting a rare case, so there's no | |
| 264 // need to over-optimize it. | |
| 265 // | |
| 266 if (update_.scroll_rect.IsEmpty()) { | |
| 267 gfx::Rect bounds = update_.GetPaintBounds(); | |
| 268 update_.paint_rects.clear(); | |
| 269 update_.paint_rects.push_back(bounds); | |
| 270 } else { | |
| 271 gfx::Rect inner, outer; | |
| 272 for (size_t i = 0; i < update_.paint_rects.size(); ++i) { | |
| 273 const gfx::Rect& existing_rect = update_.paint_rects[i]; | |
| 274 if (update_.scroll_rect.Contains(existing_rect)) { | |
| 275 inner = inner.Union(existing_rect); | |
| 276 } else { | |
| 277 outer = outer.Union(existing_rect); | |
| 278 } | |
| 279 } | |
| 280 update_.paint_rects.clear(); | |
| 281 update_.paint_rects.push_back(inner); | |
| 282 update_.paint_rects.push_back(outer); | |
| 283 } | |
| 284 } | |
| OLD | NEW |