• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
toSkIRect(const IntRect & rect)54 static SkIRect toSkIRect(const IntRect& rect) {
55     return SkIRect::MakeXYWH(rect.x(), rect.y(), rect.width(), rect.height());
56 }
57 
PictureContainer(const PictureContainer & other)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 
~PictureContainer()67 PictureContainer::~PictureContainer()
68 {
69     SkSafeUnref(picture);
70 }
71 
PicturePile(const PicturePile & other)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 
draw(SkCanvas * canvas)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         return;
89     drawWithClipRecursive(canvas, m_pile.size() - 1);
90 }
91 
clearPrerenders()92 void PicturePile::clearPrerenders()
93 {
94     for (size_t i = 0; i < m_pile.size(); i++)
95         m_pile[i].prerendered.clear();
96 }
97 
drawWithClipRecursive(SkCanvas * canvas,int index)98 void PicturePile::drawWithClipRecursive(SkCanvas* canvas, int index)
99 {
100     // TODO: Add some debug visualizations of this
101     if (index < 0)
102         return;
103     PictureContainer& pc = m_pile[index];
104     if (pc.picture && !canvas->quickReject(pc.area)) {
105         int saved = canvas->save(SkCanvas::kClip_SaveFlag);
106         if (canvas->clipRect(pc.area, SkRegion::kDifference_Op))
107             drawWithClipRecursive(canvas, index - 1);
108         canvas->restoreToCount(saved);
109         saved = canvas->save(SkCanvas::kClip_SaveFlag);
110         if (canvas->clipRect(pc.area))
111             drawPicture(canvas, pc);
112         canvas->restoreToCount(saved);
113     } else
114         drawWithClipRecursive(canvas, index - 1);
115 }
116 
117 // Used by WebViewCore
invalidate(const IntRect & dirtyRect)118 void PicturePile::invalidate(const IntRect& dirtyRect)
119 {
120     // This will typically happen if the document has been resized but we haven't
121     // drawn yet. As the first draw after a size change will do a full inval anyway,
122     // don't bother tracking individual rects
123     // TODO: Instead of clipping here, we should take the invals as given
124     // and when the size changes just inval the deltas. This prevents a full
125     // redraw for a page that grows
126     IntRect inval = dirtyRect;
127     inval.intersect(IntRect(0, 0, m_size.width(), m_size.height()));
128     if (inval.isEmpty()) {
129         ALOGV("Rejecting inval " INT_RECT_FORMAT, INT_RECT_ARGS(dirtyRect));
130         return;
131     }
132     // TODO: Support multiple non-intersecting webkit invals
133     if (m_webkitInvals.size())
134         m_webkitInvals[0].unite(inval);
135     else
136         m_webkitInvals.append(inval);
137 }
138 
setSize(const IntSize & size)139 void PicturePile::setSize(const IntSize& size)
140 {
141     if (m_size == size)
142         return;
143     IntSize oldSize = m_size;
144     m_size = size;
145     if (size.width() <= oldSize.width() && size.height() <= oldSize.height()) {
146         // We are shrinking - huzzah, nothing to do!
147         // TODO: Loop through and throw out Pictures that are now clipped out
148     } else if (oldSize.width() == size.width()) {
149         // Only changing vertically
150         IntRect rect(0, std::min(oldSize.height(), size.height()),
151                      size.width(), std::abs(oldSize.height() - size.height()));
152         invalidate(rect);
153     } else if (oldSize.height() == size.height()) {
154         // Only changing horizontally
155         IntRect rect(std::min(oldSize.width(), size.width()), 0,
156                      std::abs(oldSize.width() - size.width()), size.height());
157         invalidate(rect);
158     } else {
159         // Both width & height changed, full inval :(
160         m_pile.clear();
161         m_webkitInvals.clear();
162         if (!size.isEmpty()) {
163             IntRect area(0, 0, size.width(), size.height());
164             m_webkitInvals.append(area);
165             m_pile.append(area);
166         }
167     }
168 }
169 
updatePicturesIfNeeded(PicturePainter * painter)170 void PicturePile::updatePicturesIfNeeded(PicturePainter* painter)
171 {
172     applyWebkitInvals();
173     for (size_t i = 0; i < m_pile.size(); i++) {
174         PictureContainer& pc = m_pile[i];
175         if (pc.dirty)
176             updatePicture(painter, pc);
177     }
178 }
179 
updatePicture(PicturePainter * painter,PictureContainer & pc)180 void PicturePile::updatePicture(PicturePainter* painter, PictureContainer& pc)
181 {
182     TRACE_METHOD();
183     Picture* picture = recordPicture(painter, pc);
184     SkSafeUnref(pc.picture);
185     pc.picture = picture;
186     pc.dirty = false;
187 }
188 
reset()189 void PicturePile::reset()
190 {
191     m_size = IntSize(0,0);
192     m_pile.clear();
193     m_webkitInvals.clear();
194 }
195 
applyWebkitInvals()196 void PicturePile::applyWebkitInvals()
197 {
198     m_dirtyRegion.setEmpty();
199     if (!m_webkitInvals.size())
200         return;
201     // Build the invals (TODO: Support multiple inval regions)
202     IntRect inval = m_webkitInvals[0];
203     m_dirtyRegion.setRect(toSkIRect(inval));
204     for (size_t i = 1; i < m_webkitInvals.size(); i++) {
205         inval.unite(m_webkitInvals[i]);
206         m_dirtyRegion.op(toSkIRect(m_webkitInvals[i]), SkRegion::kUnion_Op);
207     }
208     m_webkitInvals.clear();
209     ALOGV("Webkit inval: " INT_RECT_FORMAT, INT_RECT_ARGS(inval));
210     if (inval.isEmpty())
211         return;
212 
213     // Find the overlaps
214     Vector<int> overlaps;
215     for (size_t i = 0; i < m_pile.size(); i++) {
216         PictureContainer& pc = m_pile[i];
217         if (pc.area.contains(inval)) {
218             if (pc.dirty) {
219                 ALOGV("Found already dirty intersection");
220                 return;
221             }
222             if (pc.area == inval) {
223                 appendToPile(inval);
224                 return;
225             }
226             // Don't count the base surface as an overlap
227             if (pc.area.size() != m_size)
228                 overlaps.append(i);
229         } else if (pc.area.intersects(inval))
230             overlaps.append(i);
231     }
232 
233     if (overlaps.size() >= MAX_OVERLAP_COUNT) {
234         ALOGV("Exceeds overlap count");
235         IntRect overlap = inval;
236         for (int i = (int) overlaps.size() - 1; i >= 0; i--) {
237             overlap.unite(m_pile[overlaps[i]].area);
238             m_pile.remove(overlaps[i]);
239         }
240         float overlapArea = overlap.width() * overlap.height();
241         float totalArea = m_size.width() * m_size.height();
242         if (overlapArea / totalArea > MAX_OVERLAP_AREA)
243             overlap = IntRect(0, 0, m_size.width(), m_size.height());
244         appendToPile(overlap, inval);
245         return;
246     }
247 
248     // Append!
249     appendToPile(inval);
250 }
251 
appendToPile(const IntRect & inval,const IntRect & originalInval)252 void PicturePile::appendToPile(const IntRect& inval, const IntRect& originalInval)
253 {
254     ALOGV("Adding inval " INT_RECT_FORMAT " for original inval " INT_RECT_FORMAT,
255             INT_RECT_ARGS(inval), INT_RECT_ARGS(originalInval));
256     // Remove any entries this obscures
257     for (int i = (int) m_pile.size() - 1; i >= 0; i--) {
258         if (inval.contains(m_pile[i].area))
259             m_pile.remove(i);
260     }
261     PictureContainer container(inval);
262     if (ENABLE_PRERENDERED_INVALS) {
263         container.prerendered = PrerenderedInval::create(originalInval.isEmpty()
264                                                          ? inval : originalInval);
265     }
266     m_pile.append(container);
267 }
268 
prerenderedInvalForArea(const IntRect & area)269 PrerenderedInval* PicturePile::prerenderedInvalForArea(const IntRect& area)
270 {
271     for (int i = (int) m_pile.size() - 1; i >= 0; i--) {
272         if (m_pile[i].area.intersects(area)) {
273             RefPtr<PrerenderedInval> inval = m_pile[i].prerendered;
274             if (inval.get() && inval->area.contains(area))
275                 return inval.get();
276             return 0;
277         }
278     }
279     return 0;
280 }
281 
maxZoomScale() const282 float PicturePile::maxZoomScale() const
283 {
284     float maxZoomScale = 1;
285     for (size_t i = 0; i < m_pile.size(); i++) {
286         maxZoomScale = std::max(maxZoomScale, m_pile[i].maxZoomScale);
287     }
288     return maxZoomScale;
289 }
290 
isEmpty() const291 bool PicturePile::isEmpty() const
292 {
293     for (size_t i = 0; i < m_pile.size(); i++) {
294         if (m_pile[i].picture)
295             return false;
296     }
297     return true;
298 }
299 
300 #if USE_RECORDING_CONTEXT
drawPicture(SkCanvas * canvas,PictureContainer & pc)301 void PicturePile::drawPicture(SkCanvas* canvas, PictureContainer& pc)
302 {
303     TRACE_METHOD();
304     pc.picture->draw(canvas);
305 }
306 
recordPicture(PicturePainter * painter,PictureContainer & pc)307 Picture* PicturePile::recordPicture(PicturePainter* painter, PictureContainer& pc)
308 {
309     pc.prerendered.clear(); // TODO: Support? Not needed?
310 
311     Recording* picture = new Recording();
312     WebCore::PlatformGraphicsContextRecording pgc(picture);
313     WebCore::GraphicsContext gc(&pgc);
314     painter->paintContents(&gc, pc.area);
315     pc.maxZoomScale = pgc.maxZoomScale();
316     if (pgc.isEmpty()) {
317         SkSafeUnref(picture);
318         picture = 0;
319     }
320 
321     return picture;
322 }
323 #else
drawPicture(SkCanvas * canvas,PictureContainer & pc)324 void PicturePile::drawPicture(SkCanvas* canvas, PictureContainer& pc)
325 {
326     canvas->translate(pc.area.x(), pc.area.y());
327     pc.picture->draw(canvas);
328 }
329 
recordPicture(PicturePainter * painter,PictureContainer & pc)330 Picture* PicturePile::recordPicture(PicturePainter* painter, PictureContainer& pc)
331 {
332     /* The ref counting here is a bit unusual. What happens is begin/end recording
333      * will ref/unref the recording canvas. However, 'canvas' might be pointing
334      * at an SkNWayCanvas instead of the recording canvas, which needs to be
335      * unref'd. Thus what we do is ref the recording canvas so that we can
336      * always unref whatever canvas we have at the end.
337      */
338     SkPicture* picture = new SkPicture();
339     SkCanvas* canvas = picture->beginRecording(pc.area.width(), pc.area.height(),
340             SkPicture::kUsePathBoundsForClip_RecordingFlag);
341     SkSafeRef(canvas);
342     canvas->translate(-pc.area.x(), -pc.area.y());
343     IntRect drawArea = pc.area;
344     if (pc.prerendered.get()) {
345         SkCanvas* prerender = painter->createPrerenderCanvas(pc.prerendered.get());
346         if (!prerender) {
347             ALOGV("Failed to create prerendered for " INT_RECT_FORMAT,
348                     INT_RECT_ARGS(pc.prerendered->area));
349             pc.prerendered.clear();
350         } else {
351             drawArea.unite(pc.prerendered->area);
352             SkNWayCanvas* nwayCanvas = new SkNWayCanvas(drawArea.width(), drawArea.height());
353             nwayCanvas->translate(-drawArea.x(), -drawArea.y());
354             nwayCanvas->addCanvas(canvas);
355             nwayCanvas->addCanvas(prerender);
356             SkSafeUnref(canvas);
357             SkSafeUnref(prerender);
358             canvas = nwayCanvas;
359         }
360     }
361     WebCore::PlatformGraphicsContextSkia pgc(canvas);
362     WebCore::GraphicsContext gc(&pgc);
363     ALOGV("painting picture: " INT_RECT_FORMAT, INT_RECT_ARGS(drawArea));
364     painter->paintContents(&gc, drawArea);
365 
366     // TODO: consider paint-time checking for these with SkPicture painting?
367     pc.maxZoomScale = 1e6;
368 
369     SkSafeUnref(canvas);
370     picture->endRecording();
371     return picture;
372 }
373 #endif
374 
375 } // namespace WebCore
376