Home | History | Annotate | Download | only in jni
      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 
     49 static SkIRect toSkIRect(const IntRect& rect) {
     50     return SkIRect::MakeXYWH(rect.x(), rect.y(), rect.width(), rect.height());
     51 }
     52 
     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 
     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 
     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 
     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 
     90 void PicturePile::clearPrerenders()
     91 {
     92     for (size_t i = 0; i < m_pile.size(); i++)
     93         m_pile[i].prerendered.clear();
     94 }
     95 
     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
    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 
    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 
    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 
    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 
    208 void PicturePile::reset()
    209 {
    210     m_size = IntSize(0,0);
    211     m_pile.clear();
    212     m_webkitInvals.clear();
    213 }
    214 
    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 
    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 
    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