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 "SkPicture.h"
39 #include "SkPixelRef.h"
40 #include "SkRect.h"
41 #include "SkRegion.h"
42
43 #define ENABLE_PRERENDERED_INVALS true
44 #define MAX_OVERLAP_COUNT 2
45 #define MAX_OVERLAP_AREA .7
46
47 namespace WebCore {
48
toSkIRect(const IntRect & rect)49 static SkIRect toSkIRect(const IntRect& rect) {
50 return SkIRect::MakeXYWH(rect.x(), rect.y(), rect.width(), rect.height());
51 }
52
extractClipBounds(SkCanvas * canvas,const IntSize & size)53 static IntRect extractClipBounds(SkCanvas* canvas, const IntSize& size) {
54 SkRect clip;
55 canvas->getClipBounds(&clip);
56 clip.intersect(0, 0, size.width(), size.height());
57 return enclosingIntRect(clip);
58 }
59
PicturePile(const PicturePile & other)60 PicturePile::PicturePile(const PicturePile& other)
61 : m_size(other.m_size)
62 , m_pile(other.m_pile)
63 , m_webkitInvals(other.m_webkitInvals)
64 {
65 }
66
PicturePile(SkPicture * picture)67 PicturePile::PicturePile(SkPicture* picture)
68 {
69 m_size = IntSize(picture->width(), picture->height());
70 PictureContainer pc(IntRect(0, 0, m_size.width(), m_size.height()));
71 pc.picture = picture;
72 pc.dirty = false;
73 m_pile.append(pc);
74 }
75
draw(SkCanvas * canvas)76 void PicturePile::draw(SkCanvas* canvas)
77 {
78 /* Loop down recursively, subtracting the previous clip from the SkRegion,
79 * stopping when the SkRegion is empty. This will still draw back-to-front,
80 * but it will clip out anything obscured. For performance reasons we use
81 * the rect bounds of the SkRegion for the clip, so this still can't be
82 * used for translucent surfaces
83 */
84 TRACE_METHOD();
85 IntRect clipBounds = extractClipBounds(canvas, m_size);
86 SkRegion clipRegion(toSkIRect(clipBounds));
87 drawWithClipRecursive(canvas, clipRegion, m_pile.size() - 1);
88 }
89
clearPrerenders()90 void PicturePile::clearPrerenders()
91 {
92 for (size_t i = 0; i < m_pile.size(); i++)
93 m_pile[i].prerendered.clear();
94 }
95
drawWithClipRecursive(SkCanvas * canvas,SkRegion & clipRegion,int index)96 void PicturePile::drawWithClipRecursive(SkCanvas* canvas, SkRegion& clipRegion,
97 int index)
98 {
99 // TODO: Add some debug visualizations of this
100 if (index < 0 || clipRegion.isEmpty())
101 return;
102 PictureContainer& pc = m_pile[index];
103 IntRect intersection = clipRegion.getBounds();
104 intersection.intersect(pc.area);
105 if (pc.picture && !intersection.isEmpty()) {
106 clipRegion.op(intersection, SkRegion::kDifference_Op);
107 drawWithClipRecursive(canvas, clipRegion, index - 1);
108 int saved = canvas->save();
109 canvas->clipRect(intersection);
110 canvas->translate(pc.area.x(), pc.area.y());
111 canvas->drawPicture(*pc.picture);
112 canvas->restoreToCount(saved);
113 } else
114 drawWithClipRecursive(canvas, clipRegion, 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 m_size = size;
144 // TODO: See above about just adding invals for new content
145 m_pile.clear();
146 m_webkitInvals.clear();
147 if (!size.isEmpty()) {
148 IntRect area(0, 0, size.width(), size.height());
149 m_webkitInvals.append(area);
150 m_pile.append(area);
151 }
152 }
153
updatePicturesIfNeeded(PicturePainter * painter)154 void PicturePile::updatePicturesIfNeeded(PicturePainter* painter)
155 {
156 applyWebkitInvals();
157 for (size_t i = 0; i < m_pile.size(); i++) {
158 PictureContainer& pc = m_pile[i];
159 if (pc.dirty)
160 updatePicture(painter, pc);
161 }
162 }
163
updatePicture(PicturePainter * painter,PictureContainer & pc)164 void PicturePile::updatePicture(PicturePainter* painter, PictureContainer& pc)
165 {
166 /* The ref counting here is a bit unusual. What happens is begin/end recording
167 * will ref/unref the recording canvas. However, 'canvas' might be pointing
168 * at an SkNWayCanvas instead of the recording canvas, which needs to be
169 * unref'd. Thus what we do is ref the recording canvas so that we can
170 * always unref whatever canvas we have at the end.
171 */
172 TRACE_METHOD();
173 SkPicture* picture = new SkPicture();
174 SkCanvas* canvas = picture->beginRecording(pc.area.width(), pc.area.height(),
175 SkPicture::kUsePathBoundsForClip_RecordingFlag);
176 SkSafeRef(canvas);
177 canvas->translate(-pc.area.x(), -pc.area.y());
178 IntRect drawArea = pc.area;
179 if (pc.prerendered.get()) {
180 SkCanvas* prerender = painter->createPrerenderCanvas(pc.prerendered.get());
181 if (!prerender) {
182 ALOGV("Failed to create prerendered for " INT_RECT_FORMAT,
183 INT_RECT_ARGS(pc.prerendered->area));
184 pc.prerendered.clear();
185 } else {
186 drawArea.unite(pc.prerendered->area);
187 SkNWayCanvas* nwayCanvas = new SkNWayCanvas(drawArea.width(), drawArea.height());
188 nwayCanvas->translate(-drawArea.x(), -drawArea.y());
189 nwayCanvas->addCanvas(canvas);
190 nwayCanvas->addCanvas(prerender);
191 SkSafeUnref(canvas);
192 SkSafeUnref(prerender);
193 canvas = nwayCanvas;
194 }
195 }
196 WebCore::PlatformGraphicsContextSkia pgc(canvas);
197 WebCore::GraphicsContext gc(&pgc);
198 ALOGV("painting picture: " INT_RECT_FORMAT, INT_RECT_ARGS(drawArea));
199 painter->paintContents(&gc, drawArea);
200 SkSafeUnref(canvas);
201 picture->endRecording();
202
203 SkSafeUnref(pc.picture);
204 pc.picture = picture;
205 pc.dirty = false;
206 }
207
reset()208 void PicturePile::reset()
209 {
210 m_size = IntSize(0,0);
211 m_pile.clear();
212 m_webkitInvals.clear();
213 }
214
applyWebkitInvals()215 void PicturePile::applyWebkitInvals()
216 {
217 m_dirtyRegion.setEmpty();
218 if (!m_webkitInvals.size())
219 return;
220 // Build the invals (TODO: Support multiple inval regions)
221 IntRect inval = m_webkitInvals[0];
222 m_dirtyRegion.setRect(toSkIRect(inval));
223 for (size_t i = 1; i < m_webkitInvals.size(); i++) {
224 inval.unite(m_webkitInvals[i]);
225 m_dirtyRegion.op(toSkIRect(m_webkitInvals[i]), SkRegion::kUnion_Op);
226 }
227 m_webkitInvals.clear();
228 ALOGV("Webkit inval: " INT_RECT_FORMAT, INT_RECT_ARGS(inval));
229 if (inval.isEmpty())
230 return;
231
232 // Find the overlaps
233 Vector<int> overlaps;
234 for (size_t i = 0; i < m_pile.size(); i++) {
235 PictureContainer& pc = m_pile[i];
236 if (pc.area.contains(inval)) {
237 if (pc.dirty) {
238 ALOGV("Found already dirty intersection");
239 return;
240 }
241 if (pc.area == inval) {
242 appendToPile(inval);
243 return;
244 }
245 // Don't count the base surface as an overlap
246 if (pc.area.size() != m_size)
247 overlaps.append(i);
248 } else if (pc.area.intersects(inval))
249 overlaps.append(i);
250 }
251
252 if (overlaps.size() >= MAX_OVERLAP_COUNT) {
253 ALOGV("Exceeds overlap count");
254 IntRect overlap = inval;
255 for (int i = (int) overlaps.size() - 1; i >= 0; i--) {
256 overlap.unite(m_pile[overlaps[i]].area);
257 m_pile.remove(overlaps[i]);
258 }
259 float overlapArea = overlap.width() * overlap.height();
260 float totalArea = m_size.width() * m_size.height();
261 if (overlapArea / totalArea > MAX_OVERLAP_AREA)
262 overlap = IntRect(0, 0, m_size.width(), m_size.height());
263 appendToPile(overlap, inval);
264 return;
265 }
266
267 // Append!
268 appendToPile(inval);
269 }
270
appendToPile(const IntRect & inval,const IntRect & originalInval)271 void PicturePile::appendToPile(const IntRect& inval, const IntRect& originalInval)
272 {
273 ALOGV("Adding inval " INT_RECT_FORMAT " for original inval " INT_RECT_FORMAT,
274 INT_RECT_ARGS(inval), INT_RECT_ARGS(originalInval));
275 // Remove any entries this obscures
276 for (int i = (int) m_pile.size() - 1; i >= 0; i--) {
277 if (inval.contains(m_pile[i].area))
278 m_pile.remove(i);
279 }
280 PictureContainer container(inval);
281 if (ENABLE_PRERENDERED_INVALS) {
282 container.prerendered = PrerenderedInval::create(originalInval.isEmpty()
283 ? inval : originalInval);
284 }
285 m_pile.append(container);
286 }
287
prerenderedInvalForArea(const IntRect & area)288 PrerenderedInval* PicturePile::prerenderedInvalForArea(const IntRect& area)
289 {
290 for (int i = (int) m_pile.size() - 1; i >= 0; i--) {
291 if (m_pile[i].area.intersects(area)) {
292 RefPtr<PrerenderedInval> inval = m_pile[i].prerendered;
293 if (inval.get() && inval->area.contains(area))
294 return inval.get();
295 return 0;
296 }
297 }
298 return 0;
299 }
300
301 } // namespace WebCore
302