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