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 "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