1 /* 2 * Copyright 2012, The Android Open Source Project 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * * Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * * Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #define LOG_TAG "PicturePile" 27 #define LOG_NDEBUG 1 28 29 #include "config.h" 30 #include "PicturePile.h" 31 32 #include "AndroidLog.h" 33 #include "FloatRect.h" 34 #include "GraphicsContext.h" 35 #include "PlatformGraphicsContextSkia.h" 36 #include "SkCanvas.h" 37 #include "SkNWayCanvas.h" 38 #include "SkPixelRef.h" 39 #include "SkRect.h" 40 #include "SkRegion.h" 41 42 #if USE_RECORDING_CONTEXT 43 #include "PlatformGraphicsContextRecording.h" 44 #else 45 #include "SkPicture.h" 46 #endif 47 48 #define ENABLE_PRERENDERED_INVALS true 49 #define MAX_OVERLAP_COUNT 2 50 #define MAX_OVERLAP_AREA .7 51 52 namespace WebCore { 53 54 static SkIRect toSkIRect(const IntRect& rect) { 55 return SkIRect::MakeXYWH(rect.x(), rect.y(), rect.width(), rect.height()); 56 } 57 58 PictureContainer::PictureContainer(const PictureContainer& other) 59 : picture(other.picture) 60 , area(other.area) 61 , dirty(other.dirty) 62 , prerendered(other.prerendered) 63 { 64 SkSafeRef(picture); 65 } 66 67 PictureContainer::~PictureContainer() 68 { 69 SkSafeUnref(picture); 70 } 71 72 PicturePile::PicturePile(const PicturePile& other) 73 : m_size(other.m_size) 74 , m_pile(other.m_pile) 75 , m_webkitInvals(other.m_webkitInvals) 76 { 77 } 78 79 void PicturePile::draw(SkCanvas* canvas) 80 { 81 /* Loop down recursively, subtracting the previous clip from the SkRegion, 82 * stopping when the SkRegion is empty. This will still draw back-to-front, 83 * but it will clip out anything obscured. For performance reasons we use 84 * the rect bounds of the SkRegion for the clip, so this still can't be 85 * used for translucent surfaces 86 */ 87 if (canvas->quickReject(SkRect::MakeWH(m_size.width(), m_size.height()), 88 SkCanvas::kBW_EdgeType)) 89 return; 90 drawWithClipRecursive(canvas, m_pile.size() - 1); 91 } 92 93 void PicturePile::clearPrerenders() 94 { 95 for (size_t i = 0; i < m_pile.size(); i++) 96 m_pile[i].prerendered.clear(); 97 } 98 99 void PicturePile::drawWithClipRecursive(SkCanvas* canvas, int index) 100 { 101 // TODO: Add some debug visualizations of this 102 if (index < 0) 103 return; 104 PictureContainer& pc = m_pile[index]; 105 if (pc.picture && !canvas->quickReject(pc.area, SkCanvas::kBW_EdgeType)) { 106 int saved = canvas->save(SkCanvas::kClip_SaveFlag); 107 if (canvas->clipRect(pc.area, SkRegion::kDifference_Op)) 108 drawWithClipRecursive(canvas, index - 1); 109 canvas->restoreToCount(saved); 110 saved = canvas->save(SkCanvas::kClip_SaveFlag); 111 if (canvas->clipRect(pc.area)) 112 drawPicture(canvas, pc); 113 canvas->restoreToCount(saved); 114 } else 115 drawWithClipRecursive(canvas, index - 1); 116 } 117 118 // Used by WebViewCore 119 void PicturePile::invalidate(const IntRect& dirtyRect) 120 { 121 // This will typically happen if the document has been resized but we haven't 122 // drawn yet. As the first draw after a size change will do a full inval anyway, 123 // don't bother tracking individual rects 124 // TODO: Instead of clipping here, we should take the invals as given 125 // and when the size changes just inval the deltas. This prevents a full 126 // redraw for a page that grows 127 IntRect inval = dirtyRect; 128 inval.intersect(IntRect(0, 0, m_size.width(), m_size.height())); 129 if (inval.isEmpty()) { 130 ALOGV("Rejecting inval " INT_RECT_FORMAT, INT_RECT_ARGS(dirtyRect)); 131 return; 132 } 133 // TODO: Support multiple non-intersecting webkit invals 134 if (m_webkitInvals.size()) 135 m_webkitInvals[0].unite(inval); 136 else 137 m_webkitInvals.append(inval); 138 } 139 140 void PicturePile::setSize(const IntSize& size) 141 { 142 if (m_size == size) 143 return; 144 IntSize oldSize = m_size; 145 m_size = size; 146 if (size.width() <= oldSize.width() && size.height() <= oldSize.height()) { 147 // We are shrinking - huzzah, nothing to do! 148 // TODO: Loop through and throw out Pictures that are now clipped out 149 } else if (oldSize.width() == size.width()) { 150 // Only changing vertically 151 IntRect rect(0, std::min(oldSize.height(), size.height()), 152 size.width(), std::abs(oldSize.height() - size.height())); 153 invalidate(rect); 154 } else if (oldSize.height() == size.height()) { 155 // Only changing horizontally 156 IntRect rect(std::min(oldSize.width(), size.width()), 0, 157 std::abs(oldSize.width() - size.width()), size.height()); 158 invalidate(rect); 159 } else { 160 // Both width & height changed, full inval :( 161 m_pile.clear(); 162 m_webkitInvals.clear(); 163 if (!size.isEmpty()) { 164 IntRect area(0, 0, size.width(), size.height()); 165 m_webkitInvals.append(area); 166 m_pile.append(area); 167 } 168 } 169 } 170 171 void PicturePile::updatePicturesIfNeeded(PicturePainter* painter) 172 { 173 applyWebkitInvals(); 174 for (size_t i = 0; i < m_pile.size(); i++) { 175 PictureContainer& pc = m_pile[i]; 176 if (pc.dirty) 177 updatePicture(painter, pc); 178 } 179 } 180 181 void PicturePile::updatePicture(PicturePainter* painter, PictureContainer& pc) 182 { 183 TRACE_METHOD(); 184 Picture* picture = recordPicture(painter, pc); 185 SkSafeUnref(pc.picture); 186 pc.picture = picture; 187 pc.dirty = false; 188 } 189 190 void PicturePile::reset() 191 { 192 m_size = IntSize(0,0); 193 m_pile.clear(); 194 m_webkitInvals.clear(); 195 } 196 197 void PicturePile::applyWebkitInvals() 198 { 199 m_dirtyRegion.setEmpty(); 200 if (!m_webkitInvals.size()) 201 return; 202 // Build the invals (TODO: Support multiple inval regions) 203 IntRect inval = m_webkitInvals[0]; 204 m_dirtyRegion.setRect(toSkIRect(inval)); 205 for (size_t i = 1; i < m_webkitInvals.size(); i++) { 206 inval.unite(m_webkitInvals[i]); 207 m_dirtyRegion.op(toSkIRect(m_webkitInvals[i]), SkRegion::kUnion_Op); 208 } 209 m_webkitInvals.clear(); 210 ALOGV("Webkit inval: " INT_RECT_FORMAT, INT_RECT_ARGS(inval)); 211 if (inval.isEmpty()) 212 return; 213 214 // Find the overlaps 215 Vector<int> overlaps; 216 for (size_t i = 0; i < m_pile.size(); i++) { 217 PictureContainer& pc = m_pile[i]; 218 if (pc.area.contains(inval)) { 219 if (pc.dirty) { 220 ALOGV("Found already dirty intersection"); 221 return; 222 } 223 if (pc.area == inval) { 224 appendToPile(inval); 225 return; 226 } 227 // Don't count the base surface as an overlap 228 if (pc.area.size() != m_size) 229 overlaps.append(i); 230 } else if (pc.area.intersects(inval)) 231 overlaps.append(i); 232 } 233 234 if (overlaps.size() >= MAX_OVERLAP_COUNT) { 235 ALOGV("Exceeds overlap count"); 236 IntRect overlap = inval; 237 for (int i = (int) overlaps.size() - 1; i >= 0; i--) { 238 overlap.unite(m_pile[overlaps[i]].area); 239 m_pile.remove(overlaps[i]); 240 } 241 float overlapArea = overlap.width() * overlap.height(); 242 float totalArea = m_size.width() * m_size.height(); 243 if (overlapArea / totalArea > MAX_OVERLAP_AREA) 244 overlap = IntRect(0, 0, m_size.width(), m_size.height()); 245 appendToPile(overlap, inval); 246 return; 247 } 248 249 // Append! 250 appendToPile(inval); 251 } 252 253 void PicturePile::appendToPile(const IntRect& inval, const IntRect& originalInval) 254 { 255 ALOGV("Adding inval " INT_RECT_FORMAT " for original inval " INT_RECT_FORMAT, 256 INT_RECT_ARGS(inval), INT_RECT_ARGS(originalInval)); 257 // Remove any entries this obscures 258 for (int i = (int) m_pile.size() - 1; i >= 0; i--) { 259 if (inval.contains(m_pile[i].area)) 260 m_pile.remove(i); 261 } 262 PictureContainer container(inval); 263 if (ENABLE_PRERENDERED_INVALS) { 264 container.prerendered = PrerenderedInval::create(originalInval.isEmpty() 265 ? inval : originalInval); 266 } 267 m_pile.append(container); 268 } 269 270 PrerenderedInval* PicturePile::prerenderedInvalForArea(const IntRect& area) 271 { 272 for (int i = (int) m_pile.size() - 1; i >= 0; i--) { 273 if (m_pile[i].area.intersects(area)) { 274 RefPtr<PrerenderedInval> inval = m_pile[i].prerendered; 275 if (inval.get() && inval->area.contains(area)) 276 return inval.get(); 277 return 0; 278 } 279 } 280 return 0; 281 } 282 283 float PicturePile::maxZoomScale() const 284 { 285 float maxZoomScale = 1; 286 for (size_t i = 0; i < m_pile.size(); i++) { 287 maxZoomScale = std::max(maxZoomScale, m_pile[i].maxZoomScale); 288 } 289 return maxZoomScale; 290 } 291 292 bool PicturePile::isEmpty() const 293 { 294 for (size_t i = 0; i < m_pile.size(); i++) { 295 if (m_pile[i].picture) 296 return false; 297 } 298 return true; 299 } 300 301 #if USE_RECORDING_CONTEXT 302 void PicturePile::drawPicture(SkCanvas* canvas, PictureContainer& pc) 303 { 304 TRACE_METHOD(); 305 pc.picture->draw(canvas); 306 } 307 308 Picture* PicturePile::recordPicture(PicturePainter* painter, PictureContainer& pc) 309 { 310 pc.prerendered.clear(); // TODO: Support? Not needed? 311 312 Recording* picture = new Recording(); 313 WebCore::PlatformGraphicsContextRecording pgc(picture); 314 WebCore::GraphicsContext gc(&pgc); 315 painter->paintContents(&gc, pc.area); 316 pc.maxZoomScale = pgc.maxZoomScale(); 317 if (pgc.isEmpty()) { 318 SkSafeUnref(picture); 319 picture = 0; 320 } 321 322 return picture; 323 } 324 #else 325 void PicturePile::drawPicture(SkCanvas* canvas, PictureContainer& pc) 326 { 327 canvas->translate(pc.area.x(), pc.area.y()); 328 pc.picture->draw(canvas); 329 } 330 331 Picture* PicturePile::recordPicture(PicturePainter* painter, PictureContainer& pc) 332 { 333 /* The ref counting here is a bit unusual. What happens is begin/end recording 334 * will ref/unref the recording canvas. However, 'canvas' might be pointing 335 * at an SkNWayCanvas instead of the recording canvas, which needs to be 336 * unref'd. Thus what we do is ref the recording canvas so that we can 337 * always unref whatever canvas we have at the end. 338 */ 339 SkPicture* picture = new SkPicture(); 340 SkCanvas* canvas = picture->beginRecording(pc.area.width(), pc.area.height(), 341 SkPicture::kUsePathBoundsForClip_RecordingFlag); 342 SkSafeRef(canvas); 343 canvas->translate(-pc.area.x(), -pc.area.y()); 344 IntRect drawArea = pc.area; 345 if (pc.prerendered.get()) { 346 SkCanvas* prerender = painter->createPrerenderCanvas(pc.prerendered.get()); 347 if (!prerender) { 348 ALOGV("Failed to create prerendered for " INT_RECT_FORMAT, 349 INT_RECT_ARGS(pc.prerendered->area)); 350 pc.prerendered.clear(); 351 } else { 352 drawArea.unite(pc.prerendered->area); 353 SkNWayCanvas* nwayCanvas = new SkNWayCanvas(drawArea.width(), drawArea.height()); 354 nwayCanvas->translate(-drawArea.x(), -drawArea.y()); 355 nwayCanvas->addCanvas(canvas); 356 nwayCanvas->addCanvas(prerender); 357 SkSafeUnref(canvas); 358 SkSafeUnref(prerender); 359 canvas = nwayCanvas; 360 } 361 } 362 WebCore::PlatformGraphicsContextSkia pgc(canvas); 363 WebCore::GraphicsContext gc(&pgc); 364 ALOGV("painting picture: " INT_RECT_FORMAT, INT_RECT_ARGS(drawArea)); 365 painter->paintContents(&gc, drawArea); 366 367 // TODO: consider paint-time checking for these with SkPicture painting? 368 pc.maxZoomScale = 1e6; 369 370 SkSafeUnref(canvas); 371 picture->endRecording(); 372 return picture; 373 } 374 #endif 375 376 } // namespace WebCore 377