diff --git a/include/core/SkDrawLooper.h b/include/core/SkDrawLooper.h index 4d9f718fce..e8265dbaf1 100644 --- a/include/core/SkDrawLooper.h +++ b/include/core/SkDrawLooper.h @@ -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) {} diff --git a/include/core/SkMaskFilter.h b/include/core/SkMaskFilter.h index 1f1d57070e..2808de761f 100644 --- a/include/core/SkMaskFilter.h +++ b/include/core/SkMaskFilter.h @@ -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&) {} diff --git a/include/core/SkPaint.h b/include/core/SkPaint.h index f5d98a9875..e4717afa02 100644 --- a/include/core/SkPaint.h +++ b/include/core/SkPaint.h @@ -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(this->getMaskFilter()) | - reinterpret_cast(this->getLooper()) | - reinterpret_cast(this->getRasterizer()) | + return (reinterpret_cast(this->getRasterizer()) | reinterpret_cast(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 diff --git a/src/core/SkCanvas.cpp b/src/core/SkCanvas.cpp index ed65eca725..306a0f4e45 100644 --- a/src/core/SkCanvas.cpp +++ b/src/core/SkCanvas.cpp @@ -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, - x + SkIntToScalar(bitmap.width()), - y + SkIntToScalar(bitmap.height())); - if (this->quickReject(fastBounds, paint2EdgeType(paint))) { + SkRect bounds = { + x, y, + x + SkIntToScalar(bitmap.width()), + 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; } } diff --git a/src/core/SkMaskFilter.cpp b/src/core/SkMaskFilter.cpp index 3bdca10229..42d07a6db0 100644 --- a/src/core/SkMaskFilter.cpp +++ b/src/core/SkMaskFilter.cpp @@ -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); + } +} + diff --git a/src/core/SkPaint.cpp b/src/core/SkPaint.cpp index cf25dbf9ee..7d418f4b1c 100644 --- a/src/core/SkPaint.cpp +++ b/src/core/SkPaint.cpp @@ -9,7 +9,6 @@ #include "SkPaint.h" #include "SkColorFilter.h" -#include "SkDrawLooper.h" #include "SkFontHost.h" #include "SkImageFilter.h" #include "SkMaskFilter.h" @@ -1847,23 +1846,38 @@ 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, - SkRect* storage) const { +const SkRect& SkPaint::doComputeFastBounds(const SkRect& src, + SkRect* storage) const { SkASSERT(storage); - SkASSERT(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 - radius = SK_Scalar1; - } else if (this->getStrokeJoin() == SkPaint::kMiter_Join) { - SkScalar scale = this->getStrokeMiter(); - if (scale > SK_Scalar1) { - radius = SkScalarMul(radius, scale); - } + if (this->getLooper()) { + SkASSERT(this->getLooper()->canComputeFastBounds(*this)); + this->getLooper()->computeFastBounds(*this, src, storage); + return *storage; } - storage->set(src.fLeft - radius, src.fTop - radius, - src.fRight + radius, src.fBottom + radius); + + 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 + radius = SK_Scalar1; + } else if (this->getStrokeJoin() == SkPaint::kMiter_Join) { + SkScalar scale = this->getStrokeMiter(); + if (scale > SK_Scalar1) { + radius = SkScalarMul(radius, scale); + } + } + 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; + } + } +} + diff --git a/src/core/SkPictureFlat.h b/src/core/SkPictureFlat.h index bd212b827f..983bfc5f25 100644 --- a/src/core/SkPictureFlat.h +++ b/src/core/SkPictureFlat.h @@ -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, diff --git a/src/core/SkPicturePlayback.cpp b/src/core/SkPicturePlayback.cpp index d922051e3f..c2082c7e37 100644 --- a/src/core/SkPicturePlayback.cpp +++ b/src/core/SkPicturePlayback.cpp @@ -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); diff --git a/src/core/SkPictureRecord.cpp b/src/core/SkPictureRecord.cpp index 1c57c04b23..f31065b444 100644 --- a/src/core/SkPictureRecord.cpp +++ b/src/core/SkPictureRecord.cpp @@ -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)); diff --git a/src/core/SkPictureRecord.h b/src/core/SkPictureRecord.h index 77f7173faa..ff6585d375 100644 --- a/src/core/SkPictureRecord.h +++ b/src/core/SkPictureRecord.h @@ -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& getBitmaps() const { return fBitmaps; diff --git a/src/effects/SkBlurMaskFilter.cpp b/src/effects/SkBlurMaskFilter.cpp index 4c677d4b43..83ee934c48 100644 --- a/src/effects/SkBlurMaskFilter.cpp +++ b/src/effects/SkBlurMaskFilter.cpp @@ -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)); }