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 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_area_) 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 280