Added dashing fast path

https://codereview.appspot.com/6844067/



git-svn-id: http://skia.googlecode.com/svn/trunk@6585 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/gm/dashing.cpp b/gm/dashing.cpp
index 2f1f026..e09df5f 100644
--- a/gm/dashing.cpp
+++ b/gm/dashing.cpp
@@ -171,9 +171,87 @@
 
 //////////////////////////////////////////////////////////////////////////////
 
+// Test out the on/off line dashing Chrome if fond of
+class Dashing3GM : public skiagm::GM {
+public:
+    Dashing3GM() {}
+
+protected:
+    SkString onShortName() {
+        return SkString("dashing3");
+    }
+
+    SkISize onISize() { return skiagm::make_isize(640, 480); }
+
+    // Draw a 100x100 block of dashed lines. The horizontal ones are BW
+    // while the vertical ones are AA.
+    void drawDashedLines(SkCanvas* canvas, SkScalar length, SkScalar phase) {
+        SkPaint p;
+        p.setColor(SK_ColorBLACK);
+        p.setStyle(SkPaint::kStroke_Style);
+        p.setStrokeWidth(SK_Scalar1);
+
+        SkScalar intervals[2] = { SK_Scalar1, SK_Scalar1 };
+
+        p.setPathEffect(new SkDashPathEffect(intervals, 2, phase, false));
+
+        SkPoint pts[2];
+
+        for (int y = 0; y < 100; y += 5) {
+            pts[0].set(0, SkIntToScalar(y));
+            pts[1].set(length, SkIntToScalar(y));
+
+            canvas->drawPoints(SkCanvas::kLines_PointMode, 2, pts, p);
+        }
+
+        p.setAntiAlias(true);
+
+        for (int x = 0; x < 100; x += 7) {
+            pts[0].set(SkIntToScalar(x), 0);
+            pts[1].set(SkIntToScalar(x), length);
+
+            canvas->drawPoints(SkCanvas::kLines_PointMode, 2, pts, p);
+        }
+    }
+
+    virtual void onDraw(SkCanvas* canvas) {
+        // fast path should work on this run
+        canvas->save();
+            this->drawDashedLines(canvas, 100, SK_Scalar1);
+        canvas->restore();
+
+        // non-1 phase should break the fast path
+        canvas->save();
+            canvas->translate(110, 0);
+            this->drawDashedLines(canvas, 100, SK_ScalarHalf);
+        canvas->restore();
+
+        // non-integer length should break the fast path
+        canvas->save();
+            canvas->translate(220, 0);
+            this->drawDashedLines(canvas, 99.5, SK_ScalarHalf);
+        canvas->restore();
+
+        // rotation should break the fast path
+        canvas->save();
+            canvas->translate(110+SK_ScalarRoot2Over2*100, 110+SK_ScalarRoot2Over2*100);
+            canvas->rotate(45);
+            canvas->translate(-50, -50);
+
+            this->drawDashedLines(canvas, 100, SK_Scalar1);
+        canvas->restore();
+
+    }
+
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
 static skiagm::GM* F0(void*) { return new DashingGM; }
 static skiagm::GM* F1(void*) { return new Dashing2GM; }
+static skiagm::GM* F2(void*) { return new Dashing3GM; }
 
 static skiagm::GMRegistry gR0(F0);
 static skiagm::GMRegistry gR1(F1);
+static skiagm::GMRegistry gR2(F2);
 
diff --git a/include/core/SkPathEffect.h b/include/core/SkPathEffect.h
index 5d9b68a..44056a9 100644
--- a/include/core/SkPathEffect.h
+++ b/include/core/SkPathEffect.h
@@ -12,6 +12,10 @@
 
 #include "SkFlattenable.h"
 #include "SkPaint.h"
+#include "SkPath.h"
+#include "SkPoint.h"
+#include "SkRect.h"
+#include "SkTDArray.h"
 
 class SkPath;
 
@@ -129,6 +133,47 @@
      */
     virtual void computeFastBounds(SkRect* dst, const SkRect& src);
 
+    /** \class PointData
+
+        PointData aggregates all the information needed to draw the point
+        primitives returned by an 'asPoints' call. 
+    */
+    class PointData {
+    public:
+        PointData()
+            : fFlags(0) {
+            fSize.set(SK_Scalar1, SK_Scalar1);
+            // 'asPoints' needs to initialize/fill-in 'fClipRect' if it sets 
+            // the kUseClip flag
+        };
+        ~PointData() {};
+
+        // TODO: consider using passed-in flags to limit the work asPoints does.
+        // For example, a kNoPath flag could indicate don't bother generating
+        // stamped solutions.
+
+        // Currently none of these flags are supported.
+        enum PointFlags {
+            kCircles_PointFlag            = 0x01,   // draw points as circles (instead of rects)
+            kUsePath_PointFlag            = 0x02,   // draw points as stamps of the returned path
+            kUseClip_PointFlag            = 0x04,   // apply 'fClipRect' before drawing the points
+        };
+
+        uint32_t           fFlags;      // flags that impact the drawing of the points
+        // TODO: consider replacing the TDArray with either SkData or a ptr/len field
+        SkTDArray<SkPoint> fPoints;     // the center point of each generated point
+        SkVector           fSize;       // the size to draw the points
+        SkRect             fClipRect;   // clip required to draw the points (if kUseClip is set)
+        SkPath             fPath;       // 'stamp' to be used at each point (if kUsePath is set)
+    };
+
+    /**
+     *  Does applying this path effect to 'src' yield a set of points? If so,
+     *  optionally return the points in 'results'.
+     */
+    virtual bool asPoints(PointData* results, const SkPath& src, 
+                          const SkStrokeRec&, const SkMatrix&) const;
+
 protected:
     SkPathEffect(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {}
 
diff --git a/include/effects/SkDashPathEffect.h b/include/effects/SkDashPathEffect.h
index 0397466..52557c6 100644
--- a/include/effects/SkDashPathEffect.h
+++ b/include/effects/SkDashPathEffect.h
@@ -41,6 +41,9 @@
 
     virtual bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*) SK_OVERRIDE;
 
+    virtual bool asPoints(PointData* results, const SkPath& src, 
+                          const SkStrokeRec&, const SkMatrix&) const SK_OVERRIDE;
+
     // overrides for SkFlattenable
     //  This method is not exported to java.
     virtual Factory getFactory();
diff --git a/src/core/SkDraw.cpp b/src/core/SkDraw.cpp
index 3c1b72d..f4487af 100644
--- a/src/core/SkDraw.cpp
+++ b/src/core/SkDraw.cpp
@@ -635,6 +635,39 @@
                 break;
             }
             case SkCanvas::kLines_PointMode:
+#ifndef SK_DISABLE_DASHING_OPTIMIZATION
+                if (2 == count && NULL != paint.getPathEffect()) {
+                    // most likely a dashed line - see if it is one of the ones
+                    // we can accelerate
+                    SkStrokeRec rec(paint);
+                    SkPathEffect::PointData dst;
+
+                    SkPath path;
+                    path.moveTo(pts[0]);
+                    path.lineTo(pts[1]);
+
+                    if (paint.getPathEffect()->asPoints(&dst, path, rec, *fMatrix) &&
+                        SK_Scalar1 == dst.fSize.fX && SK_Scalar1 == dst.fSize.fY &&
+                        !(SkPathEffect::PointData::kUsePath_PointFlag & dst.fFlags)) {
+                        SkPaint newP(paint);
+                        newP.setPathEffect(NULL);
+
+                        if (SkPathEffect::PointData::kCircles_PointFlag & dst.fFlags) {
+                            newP.setStrokeCap(SkPaint::kRound_Cap);
+                        } else {
+                            newP.setStrokeCap(SkPaint::kButt_Cap);
+                        }
+
+                        this->drawPoints(SkCanvas::kPoints_PointMode,
+                                         dst.fPoints.count(),
+                                         dst.fPoints.begin(),
+                                         newP,
+                                         forceUseDevice);
+                        break;
+                    }
+                }
+#endif // DISABLE_DASHING_OPTIMIZATION
+                // couldn't take fast path so fall through!
             case SkCanvas::kPolygon_PointMode: {
                 count -= 1;
                 SkPath path;
diff --git a/src/core/SkPathEffect.cpp b/src/core/SkPathEffect.cpp
index d706e8d..a64ebc7 100644
--- a/src/core/SkPathEffect.cpp
+++ b/src/core/SkPathEffect.cpp
@@ -116,6 +116,11 @@
     *dst = src;
 }
 
+bool SkPathEffect::asPoints(PointData* results, const SkPath& src, 
+                            const SkStrokeRec&, const SkMatrix&) const {
+    return false;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 SkPairPathEffect::SkPairPathEffect(SkPathEffect* pe0, SkPathEffect* pe1)
diff --git a/src/effects/SkDashPathEffect.cpp b/src/effects/SkDashPathEffect.cpp
index 3add0d7..03330f0 100644
--- a/src/effects/SkDashPathEffect.cpp
+++ b/src/effects/SkDashPathEffect.cpp
@@ -227,6 +227,82 @@
     return true;
 }
 
+// Currently asPoints is more restrictive then it needs to be. In the future
+// we need to:
+//      allow kRound_Cap capping (could allow rotations in the matrix with this)
+//      loosen restriction on initial dash length
+//      allow cases where (stroke width == interval[0]) and return size
+//      allow partial first and last pixels
+bool SkDashPathEffect::asPoints(PointData* results,
+                                const SkPath& src,
+                                const SkStrokeRec& rec,
+                                const SkMatrix& matrix) const {
+    if (rec.isFillStyle() || fInitialDashLength < 0 || SK_Scalar1 != rec.getWidth()) {
+        return false;
+    }
+
+    if (fIntervalLength != 2 || SK_Scalar1 != fIntervals[0] || SK_Scalar1 != fIntervals[1]) {
+        return false;
+    }
+
+    if (fScaleToFit || 0 != fInitialDashLength) {
+        return false;
+    }
+
+    SkPoint pts[2];
+
+    if (rec.isHairlineStyle() || !src.isLine(pts)) {
+        return false;
+    }
+
+    if (SkPaint::kButt_Cap != rec.getCap()) {
+        return false;
+    }
+
+    if (!matrix.rectStaysRect()) {
+        return false;
+    }
+
+    SkPathMeasure   meas(src, false);
+    SkScalar        length = meas.getLength();
+
+    if (!SkScalarIsInt(length)) {
+        return false;
+    }
+
+    if (NULL != results) {
+        results->fFlags = 0;    // don't use clip rect & draw rects
+        results->fSize.set(SK_Scalar1, SK_Scalar1);
+
+        SkVector tangent = pts[1] - pts[0];
+        if (tangent.isZero()) {
+            return false;
+        }
+
+        tangent.scale(SkScalarInvert(length));
+
+        SkScalar ptCount = SkScalarDiv(length, SkIntToScalar(2));
+        results->fPoints.setReserve(SkScalarCeilToInt(ptCount));
+
+        // +1 b.c. fInitialDashLength is zero so the initial segment will be skipped
+        int index = fInitialDashIndex+1;
+
+        for (SkScalar distance = SK_ScalarHalf; distance < length; distance += SK_Scalar1) {
+            SkASSERT(index <= fCount);
+
+            if (0 == index) {
+                SkScalar x0 = pts[0].fX + SkScalarMul(tangent.fX, distance);
+                SkScalar y0 = pts[0].fY + SkScalarMul(tangent.fY, distance);
+                results->fPoints.append()->set(x0, y0);
+            }
+
+            index ^= 1; // 0 -> 1 -> 0 ...
+        }
+    }
+
+    return true;
+}
+
 SkFlattenable::Factory SkDashPathEffect::getFactory() {
     return fInitialDashLength < 0 ? NULL : CreateProc;
 }