blob: 22f57a30071afe76dd2df1da114fd15ed1698865 [file] [log] [blame]
/*
* Copyright 2008, 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_NDEBUG 0
#define LOG_TAG "pictureset"
//#include <config.h>
#include "CachedPrefix.h"
#include "android_graphics.h"
#include "PictureSet.h"
#include "SkBounder.h"
#include "SkCanvas.h"
#include "SkPicture.h"
#include "SkRect.h"
#include "SkRegion.h"
#include "SkStream.h"
#include "TimeCounter.h"
#define MAX_DRAW_TIME 100
#define MIN_SPLITTABLE 400
#if PICTURE_SET_DEBUG
class MeasureStream : public SkWStream {
public:
MeasureStream() : mTotal(0) {}
virtual bool write(const void* , size_t size) {
mTotal += size;
return true;
}
size_t mTotal;
};
#endif
namespace android {
PictureSet::PictureSet()
{
mWidth = mHeight = 0;
}
PictureSet::~PictureSet()
{
clear();
}
void PictureSet::add(const Pictures* temp)
{
Pictures pictureAndBounds = *temp;
pictureAndBounds.mPicture->safeRef();
pictureAndBounds.mWroteElapsed = false;
mPictures.append(pictureAndBounds);
}
void PictureSet::add(const SkRegion& area, SkPicture* picture,
uint32_t elapsed, bool split, bool empty)
{
DBG_SET_LOGD("%p area={%d,%d,r=%d,b=%d} pict=%p elapsed=%d split=%d", this,
area.getBounds().fLeft, area.getBounds().fTop,
area.getBounds().fRight, area.getBounds().fBottom, picture,
elapsed, split);
picture->safeRef();
/* if nothing is drawn beneath part of the new picture, mark it as a base */
SkRegion diff = SkRegion(area);
Pictures* last = mPictures.end();
for (Pictures* working = mPictures.begin(); working != last; working++)
diff.op(working->mArea, SkRegion::kDifference_Op);
Pictures pictureAndBounds = {area, picture, area.getBounds(),
elapsed, split, false, diff.isEmpty() == false, empty};
mPictures.append(pictureAndBounds);
}
/*
Pictures are discarded when they are fully drawn over.
When a picture is partially drawn over, it is discarded if it is not a base, and
its rectangular bounds is reduced if it is a base.
*/
bool PictureSet::build()
{
bool rebuild = false;
DBG_SET_LOGD("%p", this);
// walk pictures back to front, removing or trimming obscured ones
SkRegion drawn;
SkRegion inval;
Pictures* first = mPictures.begin();
Pictures* last = mPictures.end();
Pictures* working;
bool checkForNewBases = false;
for (working = last; working != first; ) {
--working;
SkRegion& area = working->mArea;
SkRegion visibleArea(area);
visibleArea.op(drawn, SkRegion::kDifference_Op);
#if PICTURE_SET_DEBUG
const SkIRect& a = area.getBounds();
const SkIRect& d = drawn.getBounds();
const SkIRect& i = inval.getBounds();
const SkIRect& v = visibleArea.getBounds();
DBG_SET_LOGD("%p [%d] area={%d,%d,r=%d,b=%d} drawn={%d,%d,r=%d,b=%d}"
" inval={%d,%d,r=%d,b=%d} vis={%d,%d,r=%d,b=%d}",
this, working - first,
a.fLeft, a.fTop, a.fRight, a.fBottom,
d.fLeft, d.fTop, d.fRight, d.fBottom,
i.fLeft, i.fTop, i.fRight, i.fBottom,
v.fLeft, v.fTop, v.fRight, v.fBottom);
#endif
bool tossPicture = false;
if (working->mBase == false) {
if (area != visibleArea) {
if (visibleArea.isEmpty() == false) {
DBG_SET_LOGD("[%d] partially overdrawn", working - first);
inval.op(visibleArea, SkRegion::kUnion_Op);
} else
DBG_SET_LOGD("[%d] fully hidden", working - first);
area.setEmpty();
tossPicture = true;
}
} else {
const SkIRect& visibleBounds = visibleArea.getBounds();
const SkIRect& areaBounds = area.getBounds();
if (visibleBounds != areaBounds) {
DBG_SET_LOGD("[%d] base to be reduced", working - first);
area.setRect(visibleBounds);
checkForNewBases = tossPicture = true;
}
if (area.intersects(inval)) {
DBG_SET_LOGD("[%d] base to be redrawn", working - first);
tossPicture = true;
}
}
if (tossPicture) {
working->mPicture->safeUnref();
working->mPicture = NULL; // mark to redraw
}
if (working->mPicture == NULL) // may have been set to null elsewhere
rebuild = true;
drawn.op(area, SkRegion::kUnion_Op);
}
// collapse out empty regions
Pictures* writer = first;
for (working = first; working != last; working++) {
if (working->mArea.isEmpty())
continue;
*writer++ = *working;
}
#if PICTURE_SET_DEBUG
if ((unsigned) (writer - first) != mPictures.size())
DBG_SET_LOGD("shrink=%d (was %d)", writer - first, mPictures.size());
#endif
mPictures.shrink(writer - first);
/* When a base is discarded because it was entirely drawn over, all
remaining pictures are checked to see if one has become a base. */
if (checkForNewBases) {
drawn.setEmpty();
Pictures* last = mPictures.end();
for (working = mPictures.begin(); working != last; working++) {
SkRegion& area = working->mArea;
if (drawn.contains(working->mArea) == false) {
working->mBase = true;
DBG_SET_LOGD("[%d] new base", working - mPictures.begin());
}
drawn.op(working->mArea, SkRegion::kUnion_Op);
}
}
validate(__FUNCTION__);
return rebuild;
}
void PictureSet::checkDimensions(int width, int height, SkRegion* inval)
{
if (mWidth == width && mHeight == height)
return;
DBG_SET_LOGD("%p old:(w=%d,h=%d) new:(w=%d,h=%d)", this,
mWidth, mHeight, width, height);
if (mWidth == width && height > mHeight) { // only grew vertically
SkIRect rect;
rect.set(0, mHeight, width, height - mHeight);
inval->op(rect, SkRegion::kUnion_Op);
} else {
clear(); // if both width/height changed, clear the old cache
inval->setRect(0, 0, width, height);
}
mWidth = width;
mHeight = height;
}
void PictureSet::clear()
{
DBG_SET_LOG("");
Pictures* last = mPictures.end();
for (Pictures* working = mPictures.begin(); working != last; working++) {
working->mArea.setEmpty();
working->mPicture->safeUnref();
}
mPictures.clear();
mWidth = mHeight = 0;
}
bool PictureSet::draw(SkCanvas* canvas)
{
validate(__FUNCTION__);
Pictures* first = mPictures.begin();
Pictures* last = mPictures.end();
Pictures* working;
SkRect bounds;
if (canvas->getClipBounds(&bounds) == false)
return false;
SkIRect irect;
bounds.roundOut(&irect);
for (working = last; working != first; ) {
--working;
if (working->mArea.contains(irect)) {
#if PICTURE_SET_DEBUG
const SkIRect& b = working->mArea.getBounds();
DBG_SET_LOGD("contains working->mArea={%d,%d,%d,%d}"
" irect={%d,%d,%d,%d}", b.fLeft, b.fTop, b.fRight, b.fBottom,
irect.fLeft, irect.fTop, irect.fRight, irect.fBottom);
#endif
first = working;
break;
}
}
DBG_SET_LOGD("%p first=%d last=%d", this, first - mPictures.begin(),
last - mPictures.begin());
uint32_t maxElapsed = 0;
for (working = first; working != last; working++) {
const SkRegion& area = working->mArea;
if (area.quickReject(irect)) {
#if PICTURE_SET_DEBUG
const SkIRect& b = area.getBounds();
DBG_SET_LOGD("[%d] %p quickReject working->mArea={%d,%d,%d,%d}"
" irect={%d,%d,%d,%d}", working - first, working,
b.fLeft, b.fTop, b.fRight, b.fBottom,
irect.fLeft, irect.fTop, irect.fRight, irect.fBottom);
#endif
working->mElapsed = 0;
continue;
}
int saved = canvas->save();
SkRect pathBounds;
if (area.isComplex()) {
SkPath pathClip;
area.getBoundaryPath(&pathClip);
canvas->clipPath(pathClip);
pathBounds = pathClip.getBounds();
} else {
pathBounds.set(area.getBounds());
canvas->clipRect(pathBounds);
}
canvas->translate(pathBounds.fLeft, pathBounds.fTop);
canvas->save();
uint32_t startTime = getThreadMsec();
canvas->drawPicture(*working->mPicture);
size_t elapsed = working->mElapsed = getThreadMsec() - startTime;
working->mWroteElapsed = true;
if (maxElapsed < elapsed && (pathBounds.width() >= MIN_SPLITTABLE ||
pathBounds.height() >= MIN_SPLITTABLE))
maxElapsed = elapsed;
canvas->restoreToCount(saved);
#define DRAW_TEST_IMAGE 01
#if DRAW_TEST_IMAGE && PICTURE_SET_DEBUG
SkColor color = 0x3f000000 | (0xffffff & (unsigned) working);
canvas->drawColor(color);
SkPaint paint;
color ^= 0x00ffffff;
paint.setColor(color);
char location[256];
for (int x = area.getBounds().fLeft & ~0x3f;
x < area.getBounds().fRight; x += 0x40) {
for (int y = area.getBounds().fTop & ~0x3f;
y < area.getBounds().fBottom; y += 0x40) {
int len = snprintf(location, sizeof(location) - 1, "(%d,%d)", x, y);
canvas->drawText(location, len, x, y, paint);
}
}
#endif
DBG_SET_LOGD("[%d] %p working->mArea={%d,%d,%d,%d} elapsed=%d base=%s",
working - first, working,
area.getBounds().fLeft, area.getBounds().fTop,
area.getBounds().fRight, area.getBounds().fBottom,
working->mElapsed, working->mBase ? "true" : "false");
}
// dump(__FUNCTION__);
return maxElapsed >= MAX_DRAW_TIME;
}
void PictureSet::dump(const char* label) const
{
#if PICTURE_SET_DUMP
DBG_SET_LOGD("%p %s (%d) (w=%d,h=%d)", this, label, mPictures.size(),
mWidth, mHeight);
const Pictures* last = mPictures.end();
for (const Pictures* working = mPictures.begin(); working != last; working++) {
const SkIRect& bounds = working->mArea.getBounds();
const SkIRect& unsplit = working->mUnsplit;
MeasureStream measure;
if (working->mPicture != NULL)
working->mPicture->serialize(&measure);
LOGD(" [%d]"
" mArea.bounds={%d,%d,r=%d,b=%d}"
" mPicture=%p"
" mUnsplit={%d,%d,r=%d,b=%d}"
" mElapsed=%d"
" mSplit=%s"
" mWroteElapsed=%s"
" mBase=%s"
" pict-size=%d",
working - mPictures.begin(),
bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom,
working->mPicture,
unsplit.fLeft, unsplit.fTop, unsplit.fRight, unsplit.fBottom,
working->mElapsed, working->mSplit ? "true" : "false",
working->mWroteElapsed ? "true" : "false",
working->mBase ? "true" : "false",
measure.mTotal);
}
#endif
}
class IsEmptyBounder : public SkBounder {
virtual bool onIRect(const SkIRect& rect) {
return false;
}
};
class IsEmptyCanvas : public SkCanvas {
public:
IsEmptyCanvas(SkBounder* bounder, SkPicture* picture) :
mPicture(picture), mEmpty(true) {
setBounder(bounder);
}
void notEmpty() {
mEmpty = false;
mPicture->abortPlayback();
}
virtual void commonDrawBitmap(const SkBitmap& bitmap,
const SkMatrix& , const SkPaint& ) {
if (bitmap.width() <= 1 || bitmap.height() <= 1)
return;
DBG_SET_LOGD("abort {%d,%d}", bitmap.width(), bitmap.height());
notEmpty();
}
virtual void drawPaint(const SkPaint& paint) {
}
virtual void drawPath(const SkPath& , const SkPaint& paint) {
DBG_SET_LOG("abort");
notEmpty();
}
virtual void drawPoints(PointMode , size_t , const SkPoint [],
const SkPaint& paint) {
}
virtual void drawRect(const SkRect& , const SkPaint& paint) {
// wait for visual content
}
virtual void drawSprite(const SkBitmap& , int , int ,
const SkPaint* paint = NULL) {
DBG_SET_LOG("abort");
notEmpty();
}
virtual void drawText(const void* , size_t byteLength, SkScalar ,
SkScalar , const SkPaint& paint) {
DBG_SET_LOGD("abort %d", byteLength);
notEmpty();
}
virtual void drawPosText(const void* , size_t byteLength,
const SkPoint [], const SkPaint& paint) {
DBG_SET_LOGD("abort %d", byteLength);
notEmpty();
}
virtual void drawPosTextH(const void* , size_t byteLength,
const SkScalar [], SkScalar ,
const SkPaint& paint) {
DBG_SET_LOGD("abort %d", byteLength);
notEmpty();
}
virtual void drawTextOnPath(const void* , size_t byteLength,
const SkPath& , const SkMatrix* ,
const SkPaint& paint) {
DBG_SET_LOGD("abort %d", byteLength);
notEmpty();
}
virtual void drawPicture(SkPicture& picture) {
SkCanvas::drawPicture(picture);
}
SkPicture* mPicture;
bool mEmpty;
};
bool PictureSet::emptyPicture(SkPicture* picture) const
{
IsEmptyBounder isEmptyBounder;
IsEmptyCanvas checker(&isEmptyBounder, picture);
SkBitmap bitmap;
bitmap.setConfig(SkBitmap::kARGB_8888_Config, mWidth, mHeight);
checker.setBitmapDevice(bitmap);
checker.drawPicture(*picture);
return checker.mEmpty;
}
bool PictureSet::isEmpty() const
{
const Pictures* last = mPictures.end();
for (const Pictures* working = mPictures.begin(); working != last; working++) {
if (!working->mEmpty)
return false;
}
return true;
}
bool PictureSet::reuseSubdivided(const SkRegion& inval)
{
validate(__FUNCTION__);
if (inval.isComplex())
return false;
Pictures* working, * last = mPictures.end();
const SkIRect& invalBounds = inval.getBounds();
bool steal = false;
for (working = mPictures.begin(); working != last; working++) {
if (working->mSplit && invalBounds == working->mUnsplit) {
steal = true;
continue;
}
if (steal == false)
continue;
SkRegion temp = SkRegion(inval);
temp.op(working->mArea, SkRegion::kIntersect_Op);
if (temp.isEmpty() || temp == working->mArea)
continue;
return false;
}
if (steal == false)
return false;
for (working = mPictures.begin(); working != last; working++) {
if ((working->mSplit == false || invalBounds != working->mUnsplit) &&
inval.contains(working->mArea) == false)
continue;
working->mPicture->safeUnref();
working->mPicture = NULL;
}
return true;
}
void PictureSet::set(const PictureSet& src)
{
DBG_SET_LOGD("start %p src=%p", this, &src);
clear();
mWidth = src.mWidth;
mHeight = src.mHeight;
const Pictures* last = src.mPictures.end();
for (const Pictures* working = src.mPictures.begin(); working != last; working++)
add(working);
// dump(__FUNCTION__);
validate(__FUNCTION__);
DBG_SET_LOG("end");
}
void PictureSet::setDrawTimes(const PictureSet& src)
{
validate(__FUNCTION__);
if (mWidth != src.mWidth || mHeight != src.mHeight)
return;
Pictures* last = mPictures.end();
Pictures* working = mPictures.begin();
if (working == last)
return;
const Pictures* srcLast = src.mPictures.end();
const Pictures* srcWorking = src.mPictures.begin();
for (; srcWorking != srcLast; srcWorking++) {
if (srcWorking->mWroteElapsed == false)
continue;
while ((srcWorking->mArea != working->mArea ||
srcWorking->mPicture != working->mPicture)) {
if (++working == last)
return;
}
DBG_SET_LOGD("%p [%d] [%d] {%d,%d,r=%d,b=%d} working->mElapsed=%d <- %d",
this, working - mPictures.begin(), srcWorking - src.mPictures.begin(),
working->mArea.getBounds().fLeft, working->mArea.getBounds().fTop,
working->mArea.getBounds().fRight, working->mArea.getBounds().fBottom,
working->mElapsed, srcWorking->mElapsed);
working->mElapsed = srcWorking->mElapsed;
}
}
void PictureSet::setPicture(size_t i, SkPicture* p)
{
mPictures[i].mPicture->safeUnref();
mPictures[i].mPicture = p;
mPictures[i].mEmpty = emptyPicture(p);
}
void PictureSet::split(PictureSet* out) const
{
dump(__FUNCTION__);
DBG_SET_LOGD("%p", this);
SkIRect totalBounds;
out->mWidth = mWidth;
out->mHeight = mHeight;
totalBounds.set(0, 0, mWidth, mHeight);
SkRegion* total = new SkRegion(totalBounds);
const Pictures* last = mPictures.end();
const Pictures* working;
uint32_t balance = 0;
int multiUnsplitFastPictures = 0; // > 1 has more than 1
for (working = mPictures.begin(); working != last; working++) {
if (working->mElapsed >= MAX_DRAW_TIME || working->mSplit)
continue;
if (++multiUnsplitFastPictures > 1)
break;
}
for (working = mPictures.begin(); working != last; working++) {
uint32_t elapsed = working->mElapsed;
if (elapsed < MAX_DRAW_TIME) {
bool split = working->mSplit;
DBG_SET_LOGD("elapsed=%d working=%p total->getBounds()="
"{%d,%d,r=%d,b=%d} split=%s", elapsed, working,
total->getBounds().fLeft, total->getBounds().fTop,
total->getBounds().fRight, total->getBounds().fBottom,
split ? "true" : "false");
if (multiUnsplitFastPictures <= 1 || split) {
total->op(working->mArea, SkRegion::kDifference_Op);
out->add(working->mArea, working->mPicture, elapsed, split,
working->mEmpty);
} else if (balance < elapsed)
balance = elapsed;
continue;
}
total->op(working->mArea, SkRegion::kDifference_Op);
const SkIRect& bounds = working->mArea.getBounds();
int width = bounds.width();
int height = bounds.height();
int across = 1;
int down = 1;
while (height >= MIN_SPLITTABLE || width >= MIN_SPLITTABLE) {
if (height >= width) {
height >>= 1;
down <<= 1;
} else {
width >>= 1;
across <<= 1 ;
}
if ((elapsed >>= 1) < MAX_DRAW_TIME)
break;
}
width = bounds.width();
height = bounds.height();
int top = bounds.fTop;
for (int indexY = 0; indexY < down; ) {
int bottom = bounds.fTop + height * ++indexY / down;
int left = bounds.fLeft;
for (int indexX = 0; indexX < across; ) {
int right = bounds.fLeft + width * ++indexX / across;
SkIRect cBounds;
cBounds.set(left, top, right, bottom);
out->add(SkRegion(cBounds), (across | down) != 1 ? NULL :
working->mPicture, elapsed, true,
(across | down) != 1 ? false : working->mEmpty);
left = right;
}
top = bottom;
}
}
DBG_SET_LOGD("%p w=%d h=%d total->isEmpty()=%s multiUnsplitFastPictures=%d",
this, mWidth, mHeight, total->isEmpty() ? "true" : "false",
multiUnsplitFastPictures);
if (!total->isEmpty() && multiUnsplitFastPictures > 1)
out->add(*total, NULL, balance, false, false);
delete total;
validate(__FUNCTION__);
out->dump("split-out");
}
bool PictureSet::validate(const char* funct) const
{
bool valid = true;
#if PICTURE_SET_VALIDATE
SkRegion all;
const Pictures* first = mPictures.begin();
for (const Pictures* working = mPictures.end(); working != first; ) {
--working;
const SkPicture* pict = working->mPicture;
const SkRegion& area = working->mArea;
const SkIRect& bounds = area.getBounds();
bool localValid = false;
if (working->mUnsplit.isEmpty())
LOGD("%s working->mUnsplit.isEmpty()", funct);
else if (working->mUnsplit.contains(bounds) == false)
LOGD("%s working->mUnsplit.contains(bounds) == false", funct);
else if (working->mElapsed >= 1000)
LOGD("%s working->mElapsed >= 1000", funct);
else if ((working->mSplit & 0xfe) != 0)
LOGD("%s (working->mSplit & 0xfe) != 0", funct);
else if ((working->mWroteElapsed & 0xfe) != 0)
LOGD("%s (working->mWroteElapsed & 0xfe) != 0", funct);
else if (pict != NULL) {
int pictWidth = pict->width();
int pictHeight = pict->height();
if (pictWidth < bounds.width())
LOGD("%s pictWidth=%d < bounds.width()=%d", funct, pictWidth, bounds.width());
else if (pictHeight < bounds.height())
LOGD("%s pictHeight=%d < bounds.height()=%d", funct, pictHeight, bounds.height());
else if (working->mArea.isEmpty())
LOGD("%s working->mArea.isEmpty()", funct);
else
localValid = true;
} else
localValid = true;
working->mArea.validate();
if (localValid == false) {
if (all.contains(area) == true)
LOGD("%s all.contains(area) == true", funct);
else
localValid = true;
}
valid &= localValid;
all.op(area, SkRegion::kUnion_Op);
}
const SkIRect& allBounds = all.getBounds();
if (valid) {
valid = false;
if (allBounds.width() != mWidth)
LOGD("%s allBounds.width()=%d != mWidth=%d", funct, allBounds.width(), mWidth);
else if (allBounds.height() != mHeight)
LOGD("%s allBounds.height()=%d != mHeight=%d", funct, allBounds.height(), mHeight);
else
valid = true;
}
while (valid == false)
;
#endif
return valid;
}
} /* namespace android */