Permalink
Browse files

Fix rendering bug in pages with shadowed text.

Shadowed text currently does not get subjected to culling until
immediately prior to rendering each glyph. This is problematic
for any page with an axis greater than 32k as we can't covert
the glyph coordinates to fixed point. Additionally, this is a
large perf hit as we look at every shadowed glyph on the page
for every draw call regardless of the canvas' clip.

This fix enables shadowed text to be quickly rejected based on
the canvas' clip when the draw text command is executed.

Finally, a mirror image of this CL is currently under review for
inclusion in the open-source Skia project.

bug: 5571685
Change-Id: I5df94eccecbd7d77a08004b5cbcca02120e390f7
  • Loading branch information...
1 parent 9f67392 commit 717c009190af219a2f9e248d6fa13ad71cfdb0b1 @drWulf drWulf committed Nov 30, 2011
@@ -52,6 +52,20 @@ class SK_API SkDrawLooper : public SkFlattenable {
*/
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) {}
@@ -72,6 +72,19 @@ class SkMaskFilter : public SkFlattenable {
virtual void flatten(SkFlattenableWriteBuffer& ) {}
+ /**
+ * 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&) {}
@@ -18,7 +18,7 @@
#define SkPaint_DEFINED
#include "SkColor.h"
-#include "SkMath.h"
+#include "SkDrawLooper.h"
#include "SkXfermode.h"
class SkAutoGlyphCache;
@@ -35,7 +35,6 @@ class SkPath;
class SkPathEffect;
class SkRasterizer;
class SkShader;
-class SkDrawLooper;
class SkTypeface;
typedef const SkGlyph& (*SkDrawCacheProc)(SkGlyphCache*, const char**,
@@ -425,10 +424,11 @@ class SK_API SkPaint {
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;
}
@@ -454,8 +454,12 @@ class SK_API SkPaint {
}
*/
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.
@@ -864,8 +868,7 @@ class SK_API SkPaint {
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
@@ -57,4 +57,19 @@ bool SkMaskFilter::filterPath(const SkPath& devPath, const SkMatrix& matrix,
return true;
}
+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
@@ -17,7 +17,6 @@
#include "SkPaint.h"
#include "SkColorFilter.h"
-#include "SkDrawLooper.h"
#include "SkFontHost.h"
#include "SkMaskFilter.h"
#include "SkPathEffect.h"
@@ -1703,23 +1702,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;
+ }
+
+ 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;
}
- storage->set(src.fLeft - radius, src.fTop - radius,
- src.fRight + radius, src.fBottom + radius);
+
+ // check the mask filter
+ if (this->getMaskFilter()) {
+ this->getMaskFilter()->computeFastBounds(*storage, storage);
+ }
+
return *storage;
}
@@ -1811,3 +1825,48 @@ const SkPath* SkTextToPathIter::next(SkScalar* xpos) {
}
return NULL;
}
+
+///////////////////////////////////////////////////////////////////////////////
+
+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;
+ }
+ }
+}
@@ -25,6 +25,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,
@@ -625,6 +625,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);
@@ -242,14 +242,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);
@@ -266,7 +266,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();
}
@@ -278,23 +278,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);
@@ -305,7 +316,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));
@@ -314,6 +325,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;
@@ -340,7 +354,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));
@@ -59,8 +59,8 @@ class SkPictureRecord : public SkCanvas {
const SkPaint&);
virtual void drawData(const void*, size_t);
- void addFontMetricsTopBottom(const SkPaint& paint, SkScalar baselineY);
-
+ void addFontMetricsTopBottom(const SkPaint& paint, SkScalar minY, SkScalar maxY);
+
const SkTDArray<const SkFlatBitmap* >& getBitmaps() const {
return fBitmaps;
}
@@ -27,6 +27,7 @@ class SkBlurMaskFilterImpl : public SkMaskFilter {
// overrides from SkMaskFilter
virtual SkMask::Format getFormat();
virtual bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix& matrix, SkIPoint* margin);
+ virtual void computeFastBounds(const SkRect& src, SkRect* dst);
// overrides from SkFlattenable
// This method is not exported to java.
@@ -111,6 +112,11 @@ bool SkBlurMaskFilterImpl::filterMask(SkMask* dst, const SkMask& src, const SkMa
return false;
}
+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));
Oops, something went wrong.

0 comments on commit 717c009

Please sign in to comment.