blob: f6fe5dbfa2db179397603ba4da1e5623fb4824be [file] [log] [blame]
/*
* 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