| /* |
| * Copyright 2012, The Android Open Source Project |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #define LOG_TAG "PicturePile" |
| #define LOG_NDEBUG 1 |
| |
| #include "config.h" |
| #include "PicturePile.h" |
| |
| #include "AndroidLog.h" |
| #include "FloatRect.h" |
| #include "GraphicsContext.h" |
| #include "PlatformGraphicsContextSkia.h" |
| #include "SkCanvas.h" |
| #include "SkNWayCanvas.h" |
| #include "SkPixelRef.h" |
| #include "SkRect.h" |
| #include "SkRegion.h" |
| |
| #if USE_RECORDING_CONTEXT |
| #include "PlatformGraphicsContextRecording.h" |
| #else |
| #include "SkPicture.h" |
| #endif |
| |
| #define ENABLE_PRERENDERED_INVALS true |
| #define MAX_OVERLAP_COUNT 2 |
| #define MAX_OVERLAP_AREA .7 |
| |
| namespace WebCore { |
| |
| static SkIRect toSkIRect(const IntRect& rect) { |
| return SkIRect::MakeXYWH(rect.x(), rect.y(), rect.width(), rect.height()); |
| } |
| |
| PictureContainer::PictureContainer(const PictureContainer& other) |
| : picture(other.picture) |
| , area(other.area) |
| , dirty(other.dirty) |
| , prerendered(other.prerendered) |
| { |
| SkSafeRef(picture); |
| } |
| |
| PictureContainer::~PictureContainer() |
| { |
| SkSafeUnref(picture); |
| } |
| |
| PicturePile::PicturePile(const PicturePile& other) |
| : m_size(other.m_size) |
| , m_pile(other.m_pile) |
| , m_webkitInvals(other.m_webkitInvals) |
| { |
| } |
| |
| void PicturePile::draw(SkCanvas* canvas) |
| { |
| /* Loop down recursively, subtracting the previous clip from the SkRegion, |
| * stopping when the SkRegion is empty. This will still draw back-to-front, |
| * but it will clip out anything obscured. For performance reasons we use |
| * the rect bounds of the SkRegion for the clip, so this still can't be |
| * used for translucent surfaces |
| */ |
| if (canvas->quickReject(SkRect::MakeWH(m_size.width(), m_size.height()))) |
| return; |
| drawWithClipRecursive(canvas, m_pile.size() - 1); |
| } |
| |
| void PicturePile::clearPrerenders() |
| { |
| for (size_t i = 0; i < m_pile.size(); i++) |
| m_pile[i].prerendered.clear(); |
| } |
| |
| void PicturePile::drawWithClipRecursive(SkCanvas* canvas, int index) |
| { |
| // TODO: Add some debug visualizations of this |
| if (index < 0) |
| return; |
| PictureContainer& pc = m_pile[index]; |
| if (pc.picture && !canvas->quickReject(pc.area)) { |
| int saved = canvas->save(SkCanvas::kClip_SaveFlag); |
| if (canvas->clipRect(pc.area, SkRegion::kDifference_Op)) |
| drawWithClipRecursive(canvas, index - 1); |
| canvas->restoreToCount(saved); |
| saved = canvas->save(SkCanvas::kClip_SaveFlag); |
| if (canvas->clipRect(pc.area)) |
| drawPicture(canvas, pc); |
| canvas->restoreToCount(saved); |
| } else |
| drawWithClipRecursive(canvas, index - 1); |
| } |
| |
| // Used by WebViewCore |
| void PicturePile::invalidate(const IntRect& dirtyRect) |
| { |
| // This will typically happen if the document has been resized but we haven't |
| // drawn yet. As the first draw after a size change will do a full inval anyway, |
| // don't bother tracking individual rects |
| // TODO: Instead of clipping here, we should take the invals as given |
| // and when the size changes just inval the deltas. This prevents a full |
| // redraw for a page that grows |
| IntRect inval = dirtyRect; |
| inval.intersect(IntRect(0, 0, m_size.width(), m_size.height())); |
| if (inval.isEmpty()) { |
| ALOGV("Rejecting inval " INT_RECT_FORMAT, INT_RECT_ARGS(dirtyRect)); |
| return; |
| } |
| // TODO: Support multiple non-intersecting webkit invals |
| if (m_webkitInvals.size()) |
| m_webkitInvals[0].unite(inval); |
| else |
| m_webkitInvals.append(inval); |
| } |
| |
| void PicturePile::setSize(const IntSize& size) |
| { |
| if (m_size == size) |
| return; |
| IntSize oldSize = m_size; |
| m_size = size; |
| if (size.width() <= oldSize.width() && size.height() <= oldSize.height()) { |
| // We are shrinking - huzzah, nothing to do! |
| // TODO: Loop through and throw out Pictures that are now clipped out |
| } else if (oldSize.width() == size.width()) { |
| // Only changing vertically |
| IntRect rect(0, std::min(oldSize.height(), size.height()), |
| size.width(), std::abs(oldSize.height() - size.height())); |
| invalidate(rect); |
| } else if (oldSize.height() == size.height()) { |
| // Only changing horizontally |
| IntRect rect(std::min(oldSize.width(), size.width()), 0, |
| std::abs(oldSize.width() - size.width()), size.height()); |
| invalidate(rect); |
| } else { |
| // Both width & height changed, full inval :( |
| m_pile.clear(); |
| m_webkitInvals.clear(); |
| if (!size.isEmpty()) { |
| IntRect area(0, 0, size.width(), size.height()); |
| m_webkitInvals.append(area); |
| m_pile.append(area); |
| } |
| } |
| } |
| |
| void PicturePile::updatePicturesIfNeeded(PicturePainter* painter) |
| { |
| applyWebkitInvals(); |
| for (size_t i = 0; i < m_pile.size(); i++) { |
| PictureContainer& pc = m_pile[i]; |
| if (pc.dirty) |
| updatePicture(painter, pc); |
| } |
| } |
| |
| void PicturePile::updatePicture(PicturePainter* painter, PictureContainer& pc) |
| { |
| TRACE_METHOD(); |
| Picture* picture = recordPicture(painter, pc); |
| SkSafeUnref(pc.picture); |
| pc.picture = picture; |
| pc.dirty = false; |
| } |
| |
| void PicturePile::reset() |
| { |
| m_size = IntSize(0,0); |
| m_pile.clear(); |
| m_webkitInvals.clear(); |
| } |
| |
| void PicturePile::applyWebkitInvals() |
| { |
| m_dirtyRegion.setEmpty(); |
| if (!m_webkitInvals.size()) |
| return; |
| // Build the invals (TODO: Support multiple inval regions) |
| IntRect inval = m_webkitInvals[0]; |
| m_dirtyRegion.setRect(toSkIRect(inval)); |
| for (size_t i = 1; i < m_webkitInvals.size(); i++) { |
| inval.unite(m_webkitInvals[i]); |
| m_dirtyRegion.op(toSkIRect(m_webkitInvals[i]), SkRegion::kUnion_Op); |
| } |
| m_webkitInvals.clear(); |
| ALOGV("Webkit inval: " INT_RECT_FORMAT, INT_RECT_ARGS(inval)); |
| if (inval.isEmpty()) |
| return; |
| |
| // Find the overlaps |
| Vector<int> overlaps; |
| for (size_t i = 0; i < m_pile.size(); i++) { |
| PictureContainer& pc = m_pile[i]; |
| if (pc.area.contains(inval)) { |
| if (pc.dirty) { |
| ALOGV("Found already dirty intersection"); |
| return; |
| } |
| if (pc.area == inval) { |
| appendToPile(inval); |
| return; |
| } |
| // Don't count the base surface as an overlap |
| if (pc.area.size() != m_size) |
| overlaps.append(i); |
| } else if (pc.area.intersects(inval)) |
| overlaps.append(i); |
| } |
| |
| if (overlaps.size() >= MAX_OVERLAP_COUNT) { |
| ALOGV("Exceeds overlap count"); |
| IntRect overlap = inval; |
| for (int i = (int) overlaps.size() - 1; i >= 0; i--) { |
| overlap.unite(m_pile[overlaps[i]].area); |
| m_pile.remove(overlaps[i]); |
| } |
| float overlapArea = overlap.width() * overlap.height(); |
| float totalArea = m_size.width() * m_size.height(); |
| if (overlapArea / totalArea > MAX_OVERLAP_AREA) |
| overlap = IntRect(0, 0, m_size.width(), m_size.height()); |
| appendToPile(overlap, inval); |
| return; |
| } |
| |
| // Append! |
| appendToPile(inval); |
| } |
| |
| void PicturePile::appendToPile(const IntRect& inval, const IntRect& originalInval) |
| { |
| ALOGV("Adding inval " INT_RECT_FORMAT " for original inval " INT_RECT_FORMAT, |
| INT_RECT_ARGS(inval), INT_RECT_ARGS(originalInval)); |
| // Remove any entries this obscures |
| for (int i = (int) m_pile.size() - 1; i >= 0; i--) { |
| if (inval.contains(m_pile[i].area)) |
| m_pile.remove(i); |
| } |
| PictureContainer container(inval); |
| if (ENABLE_PRERENDERED_INVALS) { |
| container.prerendered = PrerenderedInval::create(originalInval.isEmpty() |
| ? inval : originalInval); |
| } |
| m_pile.append(container); |
| } |
| |
| PrerenderedInval* PicturePile::prerenderedInvalForArea(const IntRect& area) |
| { |
| for (int i = (int) m_pile.size() - 1; i >= 0; i--) { |
| if (m_pile[i].area.intersects(area)) { |
| RefPtr<PrerenderedInval> inval = m_pile[i].prerendered; |
| if (inval.get() && inval->area.contains(area)) |
| return inval.get(); |
| return 0; |
| } |
| } |
| return 0; |
| } |
| |
| float PicturePile::maxZoomScale() const |
| { |
| float maxZoomScale = 1; |
| for (size_t i = 0; i < m_pile.size(); i++) { |
| maxZoomScale = std::max(maxZoomScale, m_pile[i].maxZoomScale); |
| } |
| return maxZoomScale; |
| } |
| |
| bool PicturePile::isEmpty() const |
| { |
| for (size_t i = 0; i < m_pile.size(); i++) { |
| if (m_pile[i].picture) |
| return false; |
| } |
| return true; |
| } |
| |
| #if USE_RECORDING_CONTEXT |
| void PicturePile::drawPicture(SkCanvas* canvas, PictureContainer& pc) |
| { |
| TRACE_METHOD(); |
| pc.picture->draw(canvas); |
| } |
| |
| Picture* PicturePile::recordPicture(PicturePainter* painter, PictureContainer& pc) |
| { |
| pc.prerendered.clear(); // TODO: Support? Not needed? |
| |
| Recording* picture = new Recording(); |
| WebCore::PlatformGraphicsContextRecording pgc(picture); |
| WebCore::GraphicsContext gc(&pgc); |
| painter->paintContents(&gc, pc.area); |
| pc.maxZoomScale = pgc.maxZoomScale(); |
| if (pgc.isEmpty()) { |
| SkSafeUnref(picture); |
| picture = 0; |
| } |
| |
| return picture; |
| } |
| #else |
| void PicturePile::drawPicture(SkCanvas* canvas, PictureContainer& pc) |
| { |
| canvas->translate(pc.area.x(), pc.area.y()); |
| pc.picture->draw(canvas); |
| } |
| |
| Picture* PicturePile::recordPicture(PicturePainter* painter, PictureContainer& pc) |
| { |
| /* The ref counting here is a bit unusual. What happens is begin/end recording |
| * will ref/unref the recording canvas. However, 'canvas' might be pointing |
| * at an SkNWayCanvas instead of the recording canvas, which needs to be |
| * unref'd. Thus what we do is ref the recording canvas so that we can |
| * always unref whatever canvas we have at the end. |
| */ |
| SkPicture* picture = new SkPicture(); |
| SkCanvas* canvas = picture->beginRecording(pc.area.width(), pc.area.height(), |
| SkPicture::kUsePathBoundsForClip_RecordingFlag); |
| SkSafeRef(canvas); |
| canvas->translate(-pc.area.x(), -pc.area.y()); |
| IntRect drawArea = pc.area; |
| if (pc.prerendered.get()) { |
| SkCanvas* prerender = painter->createPrerenderCanvas(pc.prerendered.get()); |
| if (!prerender) { |
| ALOGV("Failed to create prerendered for " INT_RECT_FORMAT, |
| INT_RECT_ARGS(pc.prerendered->area)); |
| pc.prerendered.clear(); |
| } else { |
| drawArea.unite(pc.prerendered->area); |
| SkNWayCanvas* nwayCanvas = new SkNWayCanvas(drawArea.width(), drawArea.height()); |
| nwayCanvas->translate(-drawArea.x(), -drawArea.y()); |
| nwayCanvas->addCanvas(canvas); |
| nwayCanvas->addCanvas(prerender); |
| SkSafeUnref(canvas); |
| SkSafeUnref(prerender); |
| canvas = nwayCanvas; |
| } |
| } |
| WebCore::PlatformGraphicsContextSkia pgc(canvas); |
| WebCore::GraphicsContext gc(&pgc); |
| ALOGV("painting picture: " INT_RECT_FORMAT, INT_RECT_ARGS(drawArea)); |
| painter->paintContents(&gc, drawArea); |
| |
| // TODO: consider paint-time checking for these with SkPicture painting? |
| pc.maxZoomScale = 1e6; |
| |
| SkSafeUnref(canvas); |
| picture->endRecording(); |
| return picture; |
| } |
| #endif |
| |
| } // namespace WebCore |