extend fastbounds impls to include maskfilters and drawloopers. This allows

us to perform quick-rejects when drawing objects with shadows (esp. text).
WebKit draws shadows w/ a looper (fg and shadow) and a maskfilter on the
shadow layer.



git-svn-id: http://skia.googlecode.com/svn/trunk@3103 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
reed@google.com 2012-01-30 15:41:43 +00:00
parent bf6c1e4aff
commit 9efd9a048a
11 changed files with 186 additions and 39 deletions

View File

@ -45,6 +45,20 @@ public:
*/
virtual bool next(SkCanvas*, SkPaint* paint) = 0;
/**
* The fast bounds functions are used to enable the paint to be culled early
* in the drawing pipeline. If a subclass can support this feature it must
* return true for the canComputeFastBounds() function. If that function
* returns false then computeFastBounds behavior is undefined otherwise it
* is expected to have the following behavior. Given the parent paint and
* the parent's bounding rect the subclass must fill in and return the
* storage rect, where the storage rect is with the union of the src rect
* and the looper's bounding rect.
*/
virtual bool canComputeFastBounds(const SkPaint& paint);
virtual void computeFastBounds(const SkPaint& paint,
const SkRect& src, SkRect* dst);
protected:
SkDrawLooper() {}
SkDrawLooper(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {}

View File

@ -79,6 +79,19 @@ public:
*/
virtual BlurType asABlur(BlurInfo*) const;
/**
* The fast bounds function is used to enable the paint to be culled early
* in the drawing pipeline. This function accepts the current bounds of the
* paint as its src param and the filter adjust those bounds using its
* current mask and returns the result using the dest param. Callers are
* allowed to provide the same struct for both src and dest so each
* implementation must accomodate that behavior.
*
* The default impl calls filterMask with the src mask having no image,
* but subclasses may override this if they can compute the rect faster.
*/
virtual void computeFastBounds(const SkRect& src, SkRect* dest);
protected:
// empty for now, but lets get our subclass to remember to init us for the future
SkMaskFilter(SkFlattenableReadBuffer&) {}

View File

@ -11,6 +11,7 @@
#define SkPaint_DEFINED
#include "SkColor.h"
#include "SkDrawLooper.h"
#include "SkXfermode.h"
class SkAutoGlyphCache;
@ -28,7 +29,6 @@ class SkPath;
class SkPathEffect;
class SkRasterizer;
class SkShader;
class SkDrawLooper;
class SkTypeface;
typedef const SkGlyph& (*SkDrawCacheProc)(SkGlyphCache*, const char**,
@ -438,10 +438,11 @@ public:
the bounds computation expensive.
*/
bool canComputeFastBounds() const {
if (this->getLooper()) {
return this->getLooper()->canComputeFastBounds(*this);
}
// use bit-or since no need for early exit
return (reinterpret_cast<uintptr_t>(this->getMaskFilter()) |
reinterpret_cast<uintptr_t>(this->getLooper()) |
reinterpret_cast<uintptr_t>(this->getRasterizer()) |
return (reinterpret_cast<uintptr_t>(this->getRasterizer()) |
reinterpret_cast<uintptr_t>(this->getPathEffect())) == 0;
}
@ -467,8 +468,12 @@ public:
}
*/
const SkRect& computeFastBounds(const SkRect& orig, SkRect* storage) const {
return this->getStyle() == kFill_Style ? orig :
this->computeStrokeFastBounds(orig, storage);
if (this->getStyle() == kFill_Style &&
!this->getLooper() && !this->getMaskFilter()) {
return orig;
}
return this->doComputeFastBounds(orig, storage);
}
/** Get the paint's shader object.
@ -892,8 +897,7 @@ private:
void (*proc)(const SkDescriptor*, void*),
void* context, bool ignoreGamma = false) const;
const SkRect& computeStrokeFastBounds(const SkRect& orig,
SkRect* storage) const;
const SkRect& doComputeFastBounds(const SkRect& orig, SkRect* storage) const;
enum {
kCanonicalTextSizeForPaths = 64

View File

@ -1397,11 +1397,15 @@ void SkCanvas::drawBitmap(const SkBitmap& bitmap, SkScalar x, SkScalar y,
SkDEBUGCODE(bitmap.validate();)
if (NULL == paint || paint->canComputeFastBounds()) {
SkRect fastBounds;
fastBounds.set(x, y,
SkRect bounds = {
x, y,
x + SkIntToScalar(bitmap.width()),
y + SkIntToScalar(bitmap.height()));
if (this->quickReject(fastBounds, paint2EdgeType(paint))) {
y + SkIntToScalar(bitmap.height())
};
if (paint) {
(void)paint->computeFastBounds(bounds, &bounds);
}
if (this->quickReject(bounds, paint2EdgeType(paint))) {
return;
}
}
@ -1420,7 +1424,12 @@ void SkCanvas::internalDrawBitmapRect(const SkBitmap& bitmap, const SkIRect* src
// do this now, to avoid the cost of calling extract for RLE bitmaps
if (NULL == paint || paint->canComputeFastBounds()) {
if (this->quickReject(dst, paint2EdgeType(paint))) {
SkRect storage;
const SkRect* bounds = &dst;
if (paint) {
bounds = &paint->computeFastBounds(dst, &storage);
}
if (this->quickReject(*bounds, paint2EdgeType(paint))) {
return;
}
}

View File

@ -56,4 +56,20 @@ SkMaskFilter::BlurType SkMaskFilter::asABlur(BlurInfo*) const {
return kNone_BlurType;
}
void SkMaskFilter::computeFastBounds(const SkRect& src, SkRect* dst) {
SkMask srcM, dstM;
srcM.fImage = NULL;
src.roundOut(&srcM.fBounds);
srcM.fRowBytes = 0;
srcM.fFormat = SkMask::kA8_Format;
SkIPoint margin; // ignored
if (this->filterMask(&dstM, srcM, SkMatrix::I(), &margin)) {
dst->set(dstM.fBounds);
} else {
dst->set(srcM.fBounds);
}
}

View File

@ -9,7 +9,6 @@
#include "SkPaint.h"
#include "SkColorFilter.h"
#include "SkDrawLooper.h"
#include "SkFontHost.h"
#include "SkImageFilter.h"
#include "SkMaskFilter.h"
@ -1847,11 +1846,17 @@ bool SkPaint::getFillPath(const SkPath& src, SkPath* dst) const {
return width != 0; // return true if we're filled, or false if we're hairline (width == 0)
}
const SkRect& SkPaint::computeStrokeFastBounds(const SkRect& src,
const SkRect& SkPaint::doComputeFastBounds(const SkRect& src,
SkRect* storage) const {
SkASSERT(storage);
SkASSERT(this->getStyle() != SkPaint::kFill_Style);
if (this->getLooper()) {
SkASSERT(this->getLooper()->canComputeFastBounds(*this));
this->getLooper()->computeFastBounds(*this, src, storage);
return *storage;
}
if (this->getStyle() != SkPaint::kFill_Style) {
// since we're stroked, outset the rect by the radius (and join type)
SkScalar radius = SkScalarHalf(this->getStrokeWidth());
if (0 == radius) { // hairline
@ -1864,6 +1869,15 @@ const SkRect& SkPaint::computeStrokeFastBounds(const SkRect& src,
}
storage->set(src.fLeft - radius, src.fTop - radius,
src.fRight + radius, src.fBottom + radius);
} else {
*storage = src;
}
// check the mask filter
if (this->getMaskFilter()) {
this->getMaskFilter()->computeFastBounds(*storage, storage);
}
return *storage;
}
@ -2021,3 +2035,48 @@ bool SkImageFilter::asABlur(SkSize* sigma) const {
return false;
}
//////
bool SkDrawLooper::canComputeFastBounds(const SkPaint& paint) {
SkCanvas canvas;
this->init(&canvas);
for (;;) {
SkPaint p(paint);
if (this->next(&canvas, &p)) {
p.setLooper(NULL);
if (!p.canComputeFastBounds()) {
return false;
}
} else {
break;
}
}
return true;
}
void SkDrawLooper::computeFastBounds(const SkPaint& paint, const SkRect& src,
SkRect* dst) {
SkCanvas canvas;
this->init(&canvas);
for (bool firstTime = true;; firstTime = false) {
SkPaint p(paint);
if (this->next(&canvas, &p)) {
SkRect r(src);
p.setLooper(NULL);
p.computeFastBounds(r, &r);
canvas.getTotalMatrix().mapRect(&r);
if (firstTime) {
*dst = r;
} else {
dst->join(r);
}
} else {
break;
}
}
}

View File

@ -33,6 +33,7 @@ enum DrawType {
DRAW_PICTURE,
DRAW_POINTS,
DRAW_POS_TEXT,
DRAW_POS_TEXT_TOP_BOTTOM, // fast variant of DRAW_POS_TEXT
DRAW_POS_TEXT_H,
DRAW_POS_TEXT_H_TOP_BOTTOM, // fast variant of DRAW_POS_TEXT_H
DRAW_RECT,

View File

@ -606,6 +606,17 @@ void SkPicturePlayback::draw(SkCanvas& canvas) {
const SkPoint* pos = (const SkPoint*)fReader.skip(points * sizeof(SkPoint));
canvas.drawPosText(text.text(), text.length(), pos, paint);
} break;
case DRAW_POS_TEXT_TOP_BOTTOM: {
const SkPaint& paint = *getPaint();
getText(&text);
size_t points = getInt();
const SkPoint* pos = (const SkPoint*)fReader.skip(points * sizeof(SkPoint));
const SkScalar top = fReader.readScalar();
const SkScalar bottom = fReader.readScalar();
if (!canvas.quickRejectY(top, bottom, SkCanvas::kAA_EdgeType)) {
canvas.drawPosText(text.text(), text.length(), pos, paint);
}
} break;
case DRAW_POS_TEXT_H: {
const SkPaint& paint = *getPaint();
getText(&text);

View File

@ -287,14 +287,14 @@ void SkPictureRecord::drawSprite(const SkBitmap& bitmap, int left, int top,
}
void SkPictureRecord::addFontMetricsTopBottom(const SkPaint& paint,
SkScalar baselineY) {
SkScalar minY, SkScalar maxY) {
SkPaint::FontMetrics metrics;
paint.getFontMetrics(&metrics);
SkRect bounds;
// construct a rect so we can see any adjustments from the paint.
// we use 0,1 for left,right, just so the rect isn't empty
bounds.set(0, metrics.fTop + baselineY,
SK_Scalar1, metrics.fBottom + baselineY);
bounds.set(0, metrics.fTop + minY,
SK_Scalar1, metrics.fBottom + maxY);
(void)paint.computeFastBounds(bounds, &bounds);
// now record the top and bottom
addScalar(bounds.fTop);
@ -311,7 +311,7 @@ void SkPictureRecord::drawText(const void* text, size_t byteLength, SkScalar x,
addScalar(x);
addScalar(y);
if (fast) {
addFontMetricsTopBottom(paint, y);
addFontMetricsTopBottom(paint, y, y);
}
validate();
}
@ -323,23 +323,34 @@ void SkPictureRecord::drawPosText(const void* text, size_t byteLength,
return;
bool canUseDrawH = true;
SkScalar minY = pos[0].fY;
SkScalar maxY = pos[0].fY;
// check if the caller really should have used drawPosTextH()
{
const SkScalar firstY = pos[0].fY;
for (size_t index = 1; index < points; index++) {
if (pos[index].fY != firstY) {
canUseDrawH = false;
break;
if (pos[index].fY < minY) {
minY = pos[index].fY;
} else if (pos[index].fY > maxY) {
maxY = pos[index].fY;
}
}
}
}
bool fast = canUseDrawH && paint.canComputeFastBounds();
bool fastBounds = paint.canComputeFastBounds();
bool fast = canUseDrawH && fastBounds;
if (fast) {
addDraw(DRAW_POS_TEXT_H_TOP_BOTTOM);
} else if (canUseDrawH) {
addDraw(DRAW_POS_TEXT_H);
} else if (fastBounds) {
addDraw(DRAW_POS_TEXT_TOP_BOTTOM);
} else {
addDraw(canUseDrawH ? DRAW_POS_TEXT_H : DRAW_POS_TEXT);
addDraw(DRAW_POS_TEXT);
}
addPaint(paint);
addText(text, byteLength);
@ -350,7 +361,7 @@ void SkPictureRecord::drawPosText(const void* text, size_t byteLength,
#endif
if (canUseDrawH) {
if (fast) {
addFontMetricsTopBottom(paint, pos[0].fY);
addFontMetricsTopBottom(paint, pos[0].fY, pos[0].fY);
}
addScalar(pos[0].fY);
SkScalar* xptr = (SkScalar*)fWriter.reserve(points * sizeof(SkScalar));
@ -359,6 +370,9 @@ void SkPictureRecord::drawPosText(const void* text, size_t byteLength,
}
else {
fWriter.writeMul4(pos, points * sizeof(SkPoint));
if (fastBounds) {
addFontMetricsTopBottom(paint, minY, maxY);
}
}
#ifdef SK_DEBUG_SIZE
fPointBytes += fWriter.size() - start;
@ -385,7 +399,7 @@ void SkPictureRecord::drawPosTextH(const void* text, size_t byteLength,
size_t start = fWriter.size();
#endif
if (fast) {
addFontMetricsTopBottom(paint, constY);
addFontMetricsTopBottom(paint, constY, constY);
}
addScalar(constY);
fWriter.writeMul4(xpos, points * sizeof(SkScalar));

View File

@ -66,7 +66,7 @@ public:
const SkPaint&) SK_OVERRIDE;
virtual void drawData(const void*, size_t) SK_OVERRIDE;
void addFontMetricsTopBottom(const SkPaint& paint, SkScalar baselineY);
void addFontMetricsTopBottom(const SkPaint& paint, SkScalar minY, SkScalar maxY);
const SkTDArray<const SkFlatBitmap* >& getBitmaps() const {
return fBitmaps;

View File

@ -22,6 +22,7 @@ public:
virtual bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix&,
SkIPoint* margin) SK_OVERRIDE;
virtual BlurType asABlur(BlurInfo*) const SK_OVERRIDE;
virtual void computeFastBounds(const SkRect& src, SkRect* dest) SK_OVERRIDE;
// overrides from SkFlattenable
virtual Factory getFactory() SK_OVERRIDE;
@ -98,6 +99,11 @@ bool SkBlurMaskFilterImpl::filterMask(SkMask* dst, const SkMask& src,
blurQuality, margin);
}
void SkBlurMaskFilterImpl::computeFastBounds(const SkRect& src, SkRect* dst) {
dst->set(src.fLeft - fRadius, src.fTop - fRadius,
src.fRight + fRadius, src.fBottom + fRadius);
}
SkFlattenable* SkBlurMaskFilterImpl::CreateProc(SkFlattenableReadBuffer& buffer) {
return SkNEW_ARGS(SkBlurMaskFilterImpl, (buffer));
}