Add geometric version of spot shadow
authorJim Van Verth <jvanverth@google.com>
Fri, 27 Jan 2017 19:15:54 +0000 (14:15 -0500)
committerSkia Commit-Bot <skia-commit-bot@chromium.org>
Fri, 27 Jan 2017 19:53:18 +0000 (19:53 +0000)
BUG=skia:6119

Change-Id: Ib9770bd88f4eebd68f2d893c5788f966d89f193c
Reviewed-on: https://skia-review.googlesource.com/7585
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Jim Van Verth <jvanverth@google.com>

gn/gpu.gni
include/gpu/effects/GrBlurredEdgeFragmentProcessor.h [new file with mode: 0755]
src/effects/SkGaussianEdgeShader.cpp
src/effects/shadows/SkAmbientShadowMaskFilter.cpp
src/effects/shadows/SkSpotShadowMaskFilter.cpp
src/gpu/effects/GrBlurredEdgeFragmentProcessor.cpp [new file with mode: 0755]
src/gpu/effects/GrShadowTessellator.cpp
src/gpu/effects/GrShadowTessellator.h

index e7e629d..a53daeb 100644 (file)
@@ -42,6 +42,7 @@ skia_gpu_sources = [
   "$_include/gpu/GrTypesPriv.h",
   "$_include/gpu/GrXferProcessor.h",
 
+  "$_include/gpu/effects/GrBlurredEdgeFragmentProcessor.h",
   "$_include/gpu/effects/GrConstColorProcessor.h",
   "$_include/gpu/effects/GrCoverageSetOpXP.h",
   "$_include/gpu/effects/GrCustomXfermode.h",
@@ -296,6 +297,7 @@ skia_gpu_sources = [
   "$_src/gpu/ops/GrTestMeshDrawOp.h",
 
   "$_src/gpu/effects/Gr1DKernelEffect.h",
+  "$_src/gpu/effects/GrBlurredEdgeFragmentProcessor.cpp",
   "$_src/gpu/effects/GrConfigConversionEffect.cpp",
   "$_src/gpu/effects/GrConfigConversionEffect.h",
   "$_src/gpu/effects/GrConstColorProcessor.cpp",
diff --git a/include/gpu/effects/GrBlurredEdgeFragmentProcessor.h b/include/gpu/effects/GrBlurredEdgeFragmentProcessor.h
new file mode 100755 (executable)
index 0000000..2e52485
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrBlurredEdgeFragmentProcessor_DEFINED
+#define GrBlurredEdgeFragmentProcessor_DEFINED
+
+#include "GrFragmentProcessor.h"
+
+/**
+ * Shader for managing a blurred edge for a shadow.
+ *
+ * There are two blurring modes supported: Gaussian blur function and smoothstep function.
+ *
+ * If the primitive supports an implicit distance to the edge, the radius of the blur is specified
+ * by r & g values of the color in 14.2 fixed point. For spot shadows, we increase the stroke width
+ * to set the shadow against the shape. This pad is specified by b, also in 6.2 fixed point.
+ *
+ * When not using implicit distance, then b in the input color represents the input to the
+ * blur function.
+ *
+ * In either case, the a value represents the max final alpha.
+ */
+class GrBlurredEdgeFP : public GrFragmentProcessor {
+public:
+    enum Mode {
+        kGaussian_Mode,
+        kSmoothstep_Mode,
+
+        kLastMode = kSmoothstep_Mode
+    };
+    static const int kModeCnt = kLastMode + 1;
+
+    static sk_sp<GrFragmentProcessor> Make(Mode mode = kGaussian_Mode) {
+        return sk_sp<GrFragmentProcessor>(new GrBlurredEdgeFP(mode));
+    }
+
+    const char* name() const override { return "BlurredEdge"; }
+
+    Mode mode() const { return fMode; }
+
+private:
+    GrBlurredEdgeFP(Mode mode)
+        : INHERITED(kNone_OptimizationFlags)
+        , fMode(mode) {
+        // enable output of distance information for shape
+        this->setWillUseDistanceVectorField();
+
+        this->initClassID<GrBlurredEdgeFP>();
+    }
+
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+
+    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
+
+    bool onIsEqual(const GrFragmentProcessor&) const override;
+
+    void onComputeInvariantOutput(GrInvariantOutput* inout) const override;
+
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST;
+
+    Mode   fMode;
+
+    typedef GrFragmentProcessor INHERITED;
+};
+
+#endif
index 5bd7dca..be05e54 100644 (file)
  /** \class SkGaussianEdgeShaderImpl
  This subclass of shader applies a Gaussian to shadow edge
 
- If largerBlur is false:
- The radius of the Gaussian blur is specified by the g value of the color, in 6.2 fixed point.
- For spot shadows, we increase the stroke width to set the shadow against the shape. This pad
- is specified by b, also in 6.2 fixed point. The r value represents the max final alpha.
- The incoming alpha should be 1.
-
- If largerBlur is true:
- The radius of the Gaussian blur is specified by the r & g values of the color in 14.2 fixed point.
- For spot shadows, we increase the stroke width to set the shadow against the shape. This pad
- is specified by b, also in 6.2 fixed point. The a value represents the max final alpha.
-
- LargerBlur will be removed once Android is migrated to the updated shader.
+ If the primitive supports an implicit distance to the edge, the radius of the blur is specified
+ by r & g values of the color in 14.2 fixed point. For spot shadows, we increase the stroke width
+ to set the shadow against the shape. This pad is specified by b, also in 6.2 fixed point.
+
+ When not using implicit distance, then b in the input color represents the input to the
+ blur function.
  */
 class SkGaussianEdgeShaderImpl : public SkShader {
 public:
@@ -51,86 +45,12 @@ private:
 
 #if SK_SUPPORT_GPU
 
-#include "GrCoordTransform.h"
-#include "GrFragmentProcessor.h"
-#include "GrInvariantOutput.h"
-#include "glsl/GrGLSLFragmentProcessor.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLProgramDataManager.h"
-#include "glsl/GrGLSLUniformHandler.h"
-#include "SkGr.h"
-#include "SkGrPriv.h"
-
-class GaussianEdgeFP : public GrFragmentProcessor {
-public:
-    GaussianEdgeFP() : INHERITED(kNone_OptimizationFlags) {
-        this->initClassID<GaussianEdgeFP>();
-
-        // enable output of distance information for shape
-        this->setWillUseDistanceVectorField();
-    }
-
-    class GLSLGaussianEdgeFP : public GrGLSLFragmentProcessor {
-    public:
-        GLSLGaussianEdgeFP() {}
-
-        void emitCode(EmitArgs& args) override {
-            GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-
-            if (!args.fGpImplementsDistanceVector) {
-                fragBuilder->codeAppendf("// GP does not implement fsDistanceVector - "
-                                         " using alpha as input to GLSLGaussianEdgeFP\n");
-                fragBuilder->codeAppendf("float factor = 1.0 - %s.a;", args.fInputColor);
-                fragBuilder->codeAppend("factor = exp(-factor * factor * 4.0) - 0.018;");
-                fragBuilder->codeAppendf("%s = vec4(0.0, 0.0, 0.0, factor);", args.fOutputColor);
-            } else {
-                fragBuilder->codeAppendf("vec4 color = %s;", args.fInputColor);
-                fragBuilder->codeAppend("float radius = color.r*256.0*64.0 + color.g*64.0;");
-                fragBuilder->codeAppend("float pad = color.b*64.0;");
-
-                fragBuilder->codeAppendf("float factor = 1.0 - clamp((%s.z - pad)/radius, 0.0, 1.0);",
-                                         fragBuilder->distanceVectorName());
-                fragBuilder->codeAppend("factor = exp(-factor * factor * 4.0) - 0.018;");
-                fragBuilder->codeAppendf("%s = factor*vec4(0.0, 0.0, 0.0, color.a);",
-                                         args.fOutputColor);
-            }
-        }
-
-        static void GenKey(const GrProcessor& proc, const GrShaderCaps&, GrProcessorKeyBuilder* b) {
-            // only one shader generated currently
-            b->add32(0x0);
-        }
-
-    protected:
-        void onSetData(const GrGLSLProgramDataManager& pdman, const GrProcessor& proc) override {}
-
-        bool fLargerBlur;
-    };
-
-    void onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override {
-        GLSLGaussianEdgeFP::GenKey(*this, caps, b);
-    }
-
-    const char* name() const override { return "GaussianEdgeFP"; }
-
-    void onComputeInvariantOutput(GrInvariantOutput* inout) const override {
-        inout->mulByUnknownFourComponents();
-    }
-
-private:
-    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override {
-        return new GLSLGaussianEdgeFP();
-    }
-
-    bool onIsEqual(const GrFragmentProcessor& proc) const override { return true; }
-
-    typedef GrFragmentProcessor INHERITED;
-};
+#include "effects/GrBlurredEdgeFragmentProcessor.h"
 
 ////////////////////////////////////////////////////////////////////////////
 
 sk_sp<GrFragmentProcessor> SkGaussianEdgeShaderImpl::asFragmentProcessor(const AsFPArgs&) const {
-    return sk_make_sp<GaussianEdgeFP>();
+    return GrBlurredEdgeFP::Make(GrBlurredEdgeFP::kGaussian_Mode);
 }
 
 #endif
index e7d8d31..0672020 100755 (executable)
 #include "GrContext.h"
 #include "GrRenderTargetContext.h"
 #include "GrFragmentProcessor.h"
-#include "GrInvariantOutput.h"
 #include "GrStyle.h"
 #include "GrTexture.h"
 #include "GrTextureProxy.h"
+#include "effects/GrBlurredEdgeFragmentProcessor.h"
 #include "effects/GrShadowTessellator.h"
-#include "glsl/GrGLSLFragmentProcessor.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLProgramDataManager.h"
-#include "glsl/GrGLSLUniformHandler.h"
 #include "SkStrokeRec.h"
 #endif
 
@@ -133,56 +129,6 @@ void SkAmbientShadowMaskFilterImpl::flatten(SkWriteBuffer& buffer) const {
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-//
-// Shader for managing the shadow's edge. a in the input color represents the initial
-// edge color, which is transformed by a Gaussian function. b represents the blend factor,
-// which is multiplied by this transformed value.
-//
-class ShadowEdgeFP : public GrFragmentProcessor {
-public:
-    ShadowEdgeFP() : INHERITED(kNone_OptimizationFlags) { this->initClassID<ShadowEdgeFP>(); }
-
-    class GLSLShadowEdgeFP : public GrGLSLFragmentProcessor {
-    public:
-        GLSLShadowEdgeFP() {}
-
-        void emitCode(EmitArgs& args) override {
-            GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-
-            fragBuilder->codeAppendf("float factor = 1.0 - %s.a;", args.fInputColor);
-            fragBuilder->codeAppend("factor = exp(-factor * factor * 4.0) - 0.018;");
-            fragBuilder->codeAppendf("%s = vec4(0.0, 0.0, 0.0, %s.b*factor);", args.fOutputColor,
-                                     args.fInputColor);
-        }
-
-        static void GenKey(const GrProcessor&, const GrShaderCaps&, GrProcessorKeyBuilder*) {}
-
-    protected:
-        void onSetData(const GrGLSLProgramDataManager& pdman, const GrProcessor& proc) override {}
-    };
-
-    void onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override {
-        GLSLShadowEdgeFP::GenKey(*this, caps, b);
-    }
-
-    const char* name() const override { return "ShadowEdgeFP"; }
-
-    void onComputeInvariantOutput(GrInvariantOutput* inout) const override {
-        inout->mulByUnknownFourComponents();
-    }
-
-private:
-    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override {
-        return new GLSLShadowEdgeFP();
-    }
-
-    bool onIsEqual(const GrFragmentProcessor& proc) const override { return true; }
-
-    typedef GrFragmentProcessor INHERITED;
-};
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
 bool SkAmbientShadowMaskFilterImpl::canFilterMaskGPU(const SkRRect& devRRect,
                                                      const SkIRect& clipBounds,
                                                      const SkMatrix& ctm,
@@ -200,7 +146,7 @@ bool SkAmbientShadowMaskFilterImpl::directFilterMaskGPU(GrTextureProvider* texPr
                                                         GrPaint&& paint,
                                                         const GrClip& clip,
                                                         const SkMatrix& viewMatrix,
-                                                        const SkStrokeRec&,
+                                                        const SkStrokeRec& strokeRec,
                                                         const SkPath& path) const {
     SkASSERT(rtContext);
     // TODO: this will not handle local coordinates properly
@@ -214,6 +160,10 @@ bool SkAmbientShadowMaskFilterImpl::directFilterMaskGPU(GrTextureProvider* texPr
         return false;
     }
 
+    if (strokeRec.getStyle() != SkStrokeRec::kFill_Style) {
+        return false;
+    }
+
 #ifdef SUPPORT_FAST_PATH
     // if circle
     // TODO: switch to SkScalarNearlyEqual when either oval renderer is updated or we
@@ -233,16 +183,16 @@ bool SkAmbientShadowMaskFilterImpl::directFilterMaskGPU(GrTextureProvider* texPr
     SkScalar umbraAlpha = SkScalarInvert((1.0f+SkTMax(fOccluderHeight * kHeightFactor, 0.0f)));
     // umbraColor is the interior value, penumbraColor the exterior value.
     // umbraAlpha is the factor that is linearly interpolated from outside to inside, and
-    // then "blurred" by the ShadowEdgeFP. It is then multiplied by fAmbientAlpha to get
+    // then "blurred" by the GrBlurredEdgeFP. It is then multiplied by fAmbientAlpha to get
     // the final alpha.
-    GrColor  umbraColor = GrColorPackRGBA(0, 0, fAmbientAlpha*255.9999f, umbraAlpha*255.9999f);
-    GrColor  penumbraColor = GrColorPackRGBA(0, 0, fAmbientAlpha*255.9999f, 0);
+    GrColor  umbraColor = GrColorPackRGBA(0, 0, umbraAlpha*255.9999f, fAmbientAlpha*255.9999f);
+    GrColor  penumbraColor = GrColorPackRGBA(0, 0, 0, fAmbientAlpha*255.9999f);
 
-    GrAmbientShadowTessellator tess(SkMatrix::I(), path, radius, umbraColor, penumbraColor,
+    GrAmbientShadowTessellator tess(path, radius, umbraColor, penumbraColor,
                                 SkToBool(fFlags & SkShadowFlags::kTransparentOccluder_ShadowFlag));
 
-    sk_sp<ShadowEdgeFP> edgeFP(new ShadowEdgeFP);
-    paint.addColorFragmentProcessor(edgeFP);
+    sk_sp<GrFragmentProcessor> edgeFP = GrBlurredEdgeFP::Make(GrBlurredEdgeFP::kGaussian_Mode);
+    paint.addColorFragmentProcessor(std::move(edgeFP));
 
     rtContext->drawVertices(clip, std::move(paint), SkMatrix::I(), kTriangles_GrPrimitiveType,
                             tess.vertexCount(), tess.positions(), nullptr,
index b214cef..7a1f311 100755 (executable)
 #include "GrContext.h"
 #include "GrRenderTargetContext.h"
 #include "GrFragmentProcessor.h"
-#include "GrInvariantOutput.h"
 #include "GrStyle.h"
 #include "GrTexture.h"
 #include "GrTextureProxy.h"
-#include "glsl/GrGLSLFragmentProcessor.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLProgramDataManager.h"
-#include "glsl/GrGLSLUniformHandler.h"
+#include "effects/GrBlurredEdgeFragmentProcessor.h"
+#include "effects/GrShadowTessellator.h"
 #include "SkStrokeRec.h"
 #endif
 
@@ -162,15 +159,29 @@ bool SkSpotShadowMaskFilterImpl::canFilterMaskGPU(const SkRRect& devRRect,
 }
 
 bool SkSpotShadowMaskFilterImpl::directFilterMaskGPU(GrTextureProvider* texProvider,
-                                                     GrRenderTargetContext* drawContext,
+                                                     GrRenderTargetContext* rtContext,
                                                      GrPaint&& paint,
                                                      const GrClip& clip,
                                                      const SkMatrix& viewMatrix,
                                                      const SkStrokeRec& strokeRec,
                                                      const SkPath& path) const {
-    SkASSERT(drawContext);
+    SkASSERT(rtContext);
     // TODO: this will not handle local coordinates properly
 
+    if (fSpotAlpha <= 0.0f) {
+        return true;
+    }
+
+    // only convex paths for now
+    if (!path.isConvex()) {
+        return false;
+    }
+
+    if (strokeRec.getStyle() != SkStrokeRec::kFill_Style) {
+        return false;
+    }
+
+#ifdef SUPPORT_FAST_PATH
     // if circle
     // TODO: switch to SkScalarNearlyEqual when either oval renderer is updated or we
     // have our own GeometryProc.
@@ -183,9 +194,32 @@ bool SkSpotShadowMaskFilterImpl::directFilterMaskGPU(GrTextureProvider* texProvi
         return this->directFilterRRectMaskGPU(nullptr, drawContext, std::move(paint), clip,
                                               SkMatrix::I(), strokeRec, rrect, rrect);
     }
+#endif
 
-    // TODO
-    return false;
+    float zRatio = SkTPin(fOccluderHeight / (fLightPos.fZ - fOccluderHeight), 0.0f, 0.95f);
+
+    SkScalar radius = fLightRadius * zRatio;
+
+    // Compute the scale and translation for the spot shadow.
+    const SkScalar scale = fLightPos.fZ / (fLightPos.fZ - fOccluderHeight);
+
+    SkPoint center = SkPoint::Make(path.getBounds().centerX(), path.getBounds().centerY());
+    const SkVector spotOffset = SkVector::Make(zRatio*(center.fX - fLightPos.fX),
+                                               zRatio*(center.fY - fLightPos.fY));
+
+    GrColor  umbraColor = GrColorPackRGBA(0, 0, 255, fSpotAlpha*255.9999f);
+    GrColor  penumbraColor = GrColorPackRGBA(0, 0, 0, fSpotAlpha*255.9999f);
+    GrSpotShadowTessellator tess(path, scale, spotOffset, radius, umbraColor, penumbraColor,
+                                SkToBool(fFlags & SkShadowFlags::kTransparentOccluder_ShadowFlag));
+
+    sk_sp<GrFragmentProcessor> edgeFP = GrBlurredEdgeFP::Make(GrBlurredEdgeFP::kGaussian_Mode);
+    paint.addColorFragmentProcessor(std::move(edgeFP));
+
+    rtContext->drawVertices(clip, std::move(paint), SkMatrix::I(), kTriangles_GrPrimitiveType,
+                            tess.vertexCount(), tess.positions(), nullptr,
+                            tess.colors(), tess.indices(), tess.indexCount());
+
+    return true;
 }
 
 bool SkSpotShadowMaskFilterImpl::directFilterRRectMaskGPU(GrContext*,
@@ -196,6 +230,10 @@ bool SkSpotShadowMaskFilterImpl::directFilterRRectMaskGPU(GrContext*,
                                                           const SkStrokeRec& strokeRec,
                                                           const SkRRect& rrect,
                                                           const SkRRect& devRRect) const {
+#ifndef SUPPORT_FAST_PATH
+    return false;
+#endif
+
     // It's likely the caller has already done these checks, but we have to be sure.
     // TODO: support analytic blurring of general rrect
 
diff --git a/src/gpu/effects/GrBlurredEdgeFragmentProcessor.cpp b/src/gpu/effects/GrBlurredEdgeFragmentProcessor.cpp
new file mode 100755 (executable)
index 0000000..1441a75
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "effects/GrBlurredEdgeFragmentProcessor.h"
+#include "GrInvariantOutput.h"
+
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+
+class GLSLBlurredEdgeFP : public GrGLSLFragmentProcessor {
+public:
+    GLSLBlurredEdgeFP() {}
+
+    void emitCode(EmitArgs& args) override {
+
+        GrBlurredEdgeFP::Mode mode = args.fFp.cast<GrBlurredEdgeFP>().mode();
+
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+
+        fragBuilder->codeAppendf("vec4 color = %s;", args.fInputColor);
+        if (!args.fGpImplementsDistanceVector) {
+            fragBuilder->codeAppendf("// assuming interpolant is set in vertex colors\n");
+            fragBuilder->codeAppendf("float factor = 1.0 - color.b;");
+        } else {
+            fragBuilder->codeAppendf("// using distance to edge to compute interpolant\n");
+            fragBuilder->codeAppend("float radius = color.r*256.0*64.0 + color.g*64.0;");
+            fragBuilder->codeAppend("float pad = color.b*64.0;");
+
+            fragBuilder->codeAppendf("float factor = 1.0 - clamp((%s.z - pad)/radius, 0.0, 1.0);",
+                                     fragBuilder->distanceVectorName());
+        }
+        switch (mode) {
+            case GrBlurredEdgeFP::kGaussian_Mode:
+                fragBuilder->codeAppend("factor = exp(-factor * factor * 4.0) - 0.018;");
+                break;
+            case GrBlurredEdgeFP::kSmoothstep_Mode:
+                fragBuilder->codeAppend("factor = smoothstep(factor, 0.0, 1.0);");
+                break;
+        }
+        fragBuilder->codeAppendf("%s = factor*vec4(0.0, 0.0, 0.0, color.a);",
+                                 args.fOutputColor);
+    }
+
+protected:
+    void onSetData(const GrGLSLProgramDataManager& pdman, const GrProcessor& proc) override {}
+
+    GrBlurredEdgeFP::Mode fMode;
+};
+
+GrGLSLFragmentProcessor* GrBlurredEdgeFP::onCreateGLSLInstance() const {
+    return new GLSLBlurredEdgeFP();
+}
+
+void GrBlurredEdgeFP::onGetGLSLProcessorKey(const GrShaderCaps& caps, 
+                                            GrProcessorKeyBuilder* b) const {
+    b->add32(fMode);
+}
+
+bool GrBlurredEdgeFP::onIsEqual(const GrFragmentProcessor& other) const {
+    const GrBlurredEdgeFP& that = other.cast<GrBlurredEdgeFP>();
+    return that.fMode == fMode;
+}
+
+void GrBlurredEdgeFP::onComputeInvariantOutput(GrInvariantOutput* inout) const {
+    inout->mulByUnknownFourComponents();
+}
+
+
index 8aa91e2..a51a66a 100755 (executable)
@@ -39,8 +39,7 @@ static void compute_radial_steps(const SkVector& v1, const SkVector& v2, SkScala
     *n = SkScalarFloorToInt(steps);
 }
 
-GrAmbientShadowTessellator::GrAmbientShadowTessellator(const SkMatrix& viewMatrix,
-                                                       const SkPath& path,
+GrAmbientShadowTessellator::GrAmbientShadowTessellator(const SkPath& path,
                                                        SkScalar radius,
                                                        GrColor umbraColor,
                                                        GrColor penumbraColor,
@@ -74,16 +73,16 @@ GrAmbientShadowTessellator::GrAmbientShadowTessellator(const SkMatrix& viewMatri
     while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
         switch (verb) {
             case SkPath::kLine_Verb:
-                this->handleLine(viewMatrix, pts[1]);
+                this->handleLine(pts[1]);
                 break;
             case SkPath::kQuad_Verb:
-                this->handleQuad(viewMatrix, pts);
+                this->handleQuad(pts);
                 break;
             case SkPath::kCubic_Verb:
-                this->handleCubic(viewMatrix, pts);
+                this->handleCubic(pts);
                 break;
             case SkPath::kConic_Verb:
-                this->handleConic(viewMatrix, pts, iter.conicWeight());
+                this->handleConic(pts, iter.conicWeight());
                 break;
             case SkPath::kMove_Verb:
             case SkPath::kClose_Verb:
@@ -192,15 +191,10 @@ void GrAmbientShadowTessellator::handleLine(const SkPoint& p)  {
     SkVector normal;
     if (compute_normal(fPositions[fPrevInnerIndex], p, fRadius, fDirection, &normal)) {
         this->addArc(normal);
-        this->addEdge(p, normal);
+        this->finishArcAndAddEdge(p, normal);
     }
 }
 
-void GrAmbientShadowTessellator::handleLine(const SkMatrix& m, SkPoint p)  {
-    m.mapPoints(&p, 1);
-    this->handleLine(p);
-}
-
 void GrAmbientShadowTessellator::handleQuad(const SkPoint pts[3]) {
     int maxCount = GrPathUtils::quadraticPointCount(pts, kQuadTolerance);
     fPointBuffer.setReserve(maxCount);
@@ -213,13 +207,7 @@ void GrAmbientShadowTessellator::handleQuad(const SkPoint pts[3]) {
     }
 }
 
-void GrAmbientShadowTessellator::handleQuad(const SkMatrix& m, SkPoint pts[3]) {
-    m.mapPoints(pts, 3);
-    this->handleQuad(pts);
-}
-
-void GrAmbientShadowTessellator::handleCubic(const SkMatrix& m, SkPoint pts[4]) {
-    m.mapPoints(pts, 4);
+void GrAmbientShadowTessellator::handleCubic(SkPoint pts[4]) {
     int maxCount = GrPathUtils::cubicPointCount(pts, kCubicTolerance);
     fPointBuffer.setReserve(maxCount);
     SkPoint* target = fPointBuffer.begin();
@@ -231,8 +219,7 @@ void GrAmbientShadowTessellator::handleCubic(const SkMatrix& m, SkPoint pts[4])
     }
 }
 
-void GrAmbientShadowTessellator::handleConic(const SkMatrix& m, SkPoint pts[3], SkScalar w) {
-    m.mapPoints(pts, 3);
+void GrAmbientShadowTessellator::handleConic(SkPoint pts[3], SkScalar w) {
     SkAutoConicToQuads quadder;
     const SkPoint* quads = quadder.computeQuads(pts, w, kConicTolerance);
     SkPoint lastPoint = *(quads++);
@@ -268,7 +255,6 @@ void GrAmbientShadowTessellator::addArc(const SkVector& nextNormal) {
     }
 }
 
-
 void GrAmbientShadowTessellator::finishArcAndAddEdge(const SkPoint& nextPoint,
                                                      const SkVector& nextNormal) {
     // close out previous arc
@@ -309,3 +295,338 @@ void GrAmbientShadowTessellator::addEdge(const SkPoint& nextPoint, const SkVecto
     fPrevInnerIndex = fPositions.count() - 2;
     fPrevNormal = nextNormal;
 }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+GrSpotShadowTessellator::GrSpotShadowTessellator(const SkPath& path,
+                                                 SkScalar scale, const SkVector& translate,
+                                                 SkScalar radius,
+                                                 GrColor umbraColor, GrColor penumbraColor,
+                                                 bool /* transparent */)
+    : fRadius(radius)
+    , fUmbraColor(umbraColor)
+    , fPenumbraColor(penumbraColor)
+    , fPrevInnerIndex(-1) {
+
+    // TODO: calculate these better
+    // Outer ring: 3*numPts
+    // Inner ring: numPts
+    fPositions.setReserve(4 * path.countPoints());
+    fColors.setReserve(4 * path.countPoints());
+    // Outer ring: 12*numPts
+    // Inner ring: 0
+    fIndices.setReserve(12 * path.countPoints());
+
+    fInitPoints.setReserve(3);
+
+    fClipPolygon.setReserve(path.countPoints());
+    this->computeClipBounds(path);
+    fCentroid *= scale;
+    fCentroid += translate;
+
+    // walk around the path, tessellate and generate inner and outer rings
+    SkPath::Iter iter(path, true);
+    SkPoint pts[4];
+    SkPath::Verb verb;
+    *fPositions.push() = fCentroid;
+    *fColors.push() = fUmbraColor;
+    while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+        switch (verb) {
+            case SkPath::kLine_Verb:
+                this->handleLine(scale, translate, pts[1]);
+                break;
+            case SkPath::kQuad_Verb:
+                this->handleQuad(scale, translate, pts);
+                break;
+            case SkPath::kCubic_Verb:
+                this->handleCubic(scale, translate, pts);
+                break;
+            case SkPath::kConic_Verb:
+                this->handleConic(scale, translate, pts, iter.conicWeight());
+                break;
+            case SkPath::kMove_Verb:
+            case SkPath::kClose_Verb:
+            case SkPath::kDone_Verb:
+                break;
+        }
+    }
+
+    SkVector normal;
+    if (compute_normal(fPrevPoint, fFirstPoint, fRadius, fDirection,
+                        &normal)) {
+        this->addArc(normal);
+
+        // close out previous arc
+        *fPositions.push() = fPrevPoint + normal;
+        *fColors.push() = fPenumbraColor;
+        *fIndices.push() = fPrevInnerIndex;
+        *fIndices.push() = fPositions.count() - 2;
+        *fIndices.push() = fPositions.count() - 1;
+
+        // add final edge
+        *fPositions.push() = fFirstPoint + normal;
+        *fColors.push() = fPenumbraColor;
+
+        *fIndices.push() = fPrevInnerIndex;
+        *fIndices.push() = fPositions.count() - 2;
+        *fIndices.push() = fFirstVertex;
+
+        *fIndices.push() = fPositions.count() - 2;
+        *fIndices.push() = fPositions.count() - 1;
+        *fIndices.push() = fFirstVertex;
+
+        // add to center fan
+        *fIndices.push() = 0;
+        *fIndices.push() = fPrevInnerIndex;
+        *fIndices.push() = fFirstVertex;
+    }
+
+    // final fan
+    if (fPositions.count() >= 3) {
+        fPrevInnerIndex = fFirstVertex;
+        fPrevPoint = fFirstPoint;
+        fPrevNormal = normal;
+        this->addArc(fFirstNormal);
+
+        *fIndices.push() = fFirstVertex;
+        *fIndices.push() = fPositions.count() - 1;
+        *fIndices.push() = fFirstVertex + 1;
+    }
+}
+
+void GrSpotShadowTessellator::computeClipBounds(const SkPath& path) {
+    // walk around the path and compute clip polygon
+    // if original path is transparent, will accumulate sum of points for centroid
+    SkPath::Iter iter(path, true);
+    SkPoint pts[4];
+    SkPath::Verb verb;
+
+    fCentroid = SkPoint::Make(0, 0);
+    int centroidCount = 0;
+    fClipPolygon.reset();
+
+    while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+        switch (verb) {
+            case SkPath::kMove_Verb:
+                break;
+            case SkPath::kLine_Verb:
+                fCentroid += pts[1];
+                centroidCount++;
+                *fClipPolygon.push() = pts[1];
+                break;
+            case SkPath::kQuad_Verb:
+                fCentroid += pts[1];
+                fCentroid += pts[2];
+                centroidCount += 2;
+                *fClipPolygon.push() = pts[2];
+                break;
+            case SkPath::kConic_Verb:
+                fCentroid += pts[1];
+                fCentroid += pts[2];
+                centroidCount += 2;
+                *fClipPolygon.push() = pts[2];
+                break;
+            case SkPath::kCubic_Verb:
+                fCentroid += pts[1];
+                fCentroid += pts[2];
+                fCentroid += pts[3];
+                centroidCount += 3;
+                *fClipPolygon.push() = pts[3];
+                break;
+            case SkPath::kClose_Verb:
+                break;
+            default:
+                SkDEBUGFAIL("unknown verb");
+        }
+    }
+
+    fCentroid *= SkScalarInvert(centroidCount);
+}
+
+void GrSpotShadowTessellator::mapPoints(SkScalar scale, const SkVector& xlate,
+                                        SkPoint* pts, int count) {
+    // TODO: vectorize
+    for (int i = 0; i < count; ++i) {
+        pts[i] *= scale;
+        pts[i] += xlate;
+    }
+}
+
+void GrSpotShadowTessellator::handleLine(const SkPoint& p) {
+    if (fInitPoints.count() < 2) {
+        *fInitPoints.push() = p;
+        return;
+    }
+
+    if (fInitPoints.count() == 2) {
+        // determine if cw or ccw
+        SkVector v0 = fInitPoints[1] - fInitPoints[0];
+        SkVector v1 = p - fInitPoints[0];
+        SkScalar perpDot = v0.fX*v1.fY - v0.fY*v1.fX;
+        if (SkScalarNearlyZero(perpDot)) {
+            // nearly parallel, just treat as straight line and continue
+            fInitPoints[1] = p;
+            return;
+        }
+
+        // if perpDot > 0, winding is ccw
+        fDirection = (perpDot > 0) ? -1 : 1;
+
+        // add first quad
+        if (!compute_normal(fInitPoints[0], fInitPoints[1], fRadius, fDirection,
+                            &fFirstNormal)) {
+            // first two points are incident, make the third point the second and continue
+            fInitPoints[1] = p;
+            return;
+        }
+
+        fFirstPoint = fInitPoints[0];
+        fFirstVertex = fPositions.count();
+        fPrevNormal = fFirstNormal;
+        fPrevPoint = fFirstPoint;
+        fPrevInnerIndex = fFirstVertex;
+
+        this->addInnerPoint(fFirstPoint, fUmbraColor, fRadius);
+        SkPoint newPoint = fFirstPoint + fFirstNormal;
+        *fPositions.push() = newPoint;
+        *fColors.push() = fPenumbraColor;
+        this->addEdge(fInitPoints[1], fFirstNormal);
+
+        // to ensure we skip this block next time
+        *fInitPoints.push() = p;
+    }
+
+    SkVector normal;
+    if (compute_normal(fPrevPoint, p, fRadius, fDirection, &normal)) {
+        this->addArc(normal);
+        this->finishArcAndAddEdge(p, normal);
+    }
+}
+
+void GrSpotShadowTessellator::handleLine(SkScalar scale, const SkVector& xlate, SkPoint p) {
+    this->mapPoints(scale, xlate, &p, 1);
+    this->handleLine(p);
+}
+
+void GrSpotShadowTessellator::handleQuad(const SkPoint pts[3]) {
+    int maxCount = GrPathUtils::quadraticPointCount(pts, kQuadTolerance);
+    fPointBuffer.setReserve(maxCount);
+    SkPoint* target = fPointBuffer.begin();
+    int count = GrPathUtils::generateQuadraticPoints(pts[0], pts[1], pts[2],
+                                                     kQuadTolerance, &target, maxCount);
+    fPointBuffer.setCount(count);
+    for (int i = 0; i < count; i++) {
+        this->handleLine(fPointBuffer[i]);
+    }
+}
+
+void GrSpotShadowTessellator::handleQuad(SkScalar scale, const SkVector& xlate, SkPoint pts[3]) {
+    this->mapPoints(scale, xlate, pts, 3);
+    this->handleQuad(pts);
+}
+
+void GrSpotShadowTessellator::handleCubic(SkScalar scale, const SkVector& xlate, SkPoint pts[4]) {
+    this->mapPoints(scale, xlate, pts, 4);
+    int maxCount = GrPathUtils::cubicPointCount(pts, kCubicTolerance);
+    fPointBuffer.setReserve(maxCount);
+    SkPoint* target = fPointBuffer.begin();
+    int count = GrPathUtils::generateCubicPoints(pts[0], pts[1], pts[2], pts[3],
+                                                 kCubicTolerance, &target, maxCount);
+    fPointBuffer.setCount(count);
+    for (int i = 0; i < count; i++) {
+        this->handleLine(fPointBuffer[i]);
+    }
+}
+
+void GrSpotShadowTessellator::handleConic(SkScalar scale, const SkVector& xlate,
+                                          SkPoint pts[3], SkScalar w) {
+    this->mapPoints(scale, xlate, pts, 3);
+    SkAutoConicToQuads quadder;
+    const SkPoint* quads = quadder.computeQuads(pts, w, kConicTolerance);
+    SkPoint lastPoint = *(quads++);
+    int count = quadder.countQuads();
+    for (int i = 0; i < count; ++i) {
+        SkPoint quadPts[3];
+        quadPts[0] = lastPoint;
+        quadPts[1] = quads[0];
+        quadPts[2] = i == count - 1 ? pts[2] : quads[1];
+        this->handleQuad(quadPts);
+        lastPoint = quadPts[2];
+        quads += 2;
+    }
+}
+
+void GrSpotShadowTessellator::addInnerPoint(const SkPoint& pathPoint, GrColor umbraColor,
+                                            SkScalar radius) {
+    SkVector v = fCentroid - pathPoint;
+    SkScalar distance = v.length();
+    if (distance < radius) {
+        *fPositions.push() = fCentroid;
+        *fColors.push() = umbraColor; // fix this
+        // TODO: deal with fanning from centroid
+    } else {
+        SkScalar t = radius / distance;
+        v *= t;
+        SkPoint innerPoint = pathPoint + v;
+        *fPositions.push() = innerPoint;
+        *fColors.push() = umbraColor;
+    }
+    fPrevPoint = pathPoint;
+}
+
+void GrSpotShadowTessellator::addArc(const SkVector& nextNormal) {
+    // fill in fan from previous quad
+    SkScalar rotSin, rotCos;
+    int numSteps;
+    compute_radial_steps(fPrevNormal, nextNormal, fRadius, &rotSin, &rotCos, &numSteps);
+    SkVector prevNormal = fPrevNormal;
+    for (int i = 0; i < numSteps; ++i) {
+        SkVector nextNormal;
+        nextNormal.fX = prevNormal.fX*rotCos - prevNormal.fY*rotSin;
+        nextNormal.fY = prevNormal.fY*rotCos + prevNormal.fX*rotSin;
+        *fPositions.push() = fPrevPoint + nextNormal;
+        *fColors.push() = fPenumbraColor;
+        *fIndices.push() = fPrevInnerIndex;
+        *fIndices.push() = fPositions.count() - 2;
+        *fIndices.push() = fPositions.count() - 1;
+
+        prevNormal = nextNormal;
+    }
+}
+
+void GrSpotShadowTessellator::finishArcAndAddEdge(const SkPoint& nextPoint,
+                                                  const SkVector& nextNormal) {
+    // close out previous arc
+    SkPoint newPoint = fPrevPoint + nextNormal;
+    *fPositions.push() = newPoint;
+    *fColors.push() = fPenumbraColor;
+    *fIndices.push() = fPrevInnerIndex;
+    *fIndices.push() = fPositions.count() - 2;
+    *fIndices.push() = fPositions.count() - 1;
+
+    this->addEdge(nextPoint, nextNormal);
+}
+
+void GrSpotShadowTessellator::addEdge(const SkPoint& nextPoint, const SkVector& nextNormal) {
+    // add next quad
+    this->addInnerPoint(nextPoint, fUmbraColor, fRadius);
+    SkPoint newPoint = nextPoint + nextNormal;
+    *fPositions.push() = newPoint;
+    *fColors.push() = fPenumbraColor;
+
+    *fIndices.push() = fPrevInnerIndex;
+    *fIndices.push() = fPositions.count() - 3;
+    *fIndices.push() = fPositions.count() - 2;
+
+    *fIndices.push() = fPositions.count() - 3;
+    *fIndices.push() = fPositions.count() - 1;
+    *fIndices.push() = fPositions.count() - 2;
+
+    // add to center fan
+    *fIndices.push() = 0;
+    *fIndices.push() = fPrevInnerIndex;
+    *fIndices.push() = fPositions.count() - 2;
+
+    fPrevInnerIndex = fPositions.count() - 2;
+    fPrevNormal = nextNormal;
+}
index 6d42f49..c2acde7 100755 (executable)
@@ -16,6 +16,8 @@
 class SkMatrix;
 class SkPath;
 
+// TODO: derive these two classes from a base class containing common elements
+
 /**
  * This class generates an ambient shadow for a path by walking the path, outsetting by the
  * radius, and setting inner and outer colors to umbraColor and penumbraColor, respectively.
@@ -23,8 +25,8 @@ class SkPath;
  */
 class GrAmbientShadowTessellator {
 public:
-    GrAmbientShadowTessellator(const SkMatrix& viewMatrix, const SkPath& path, SkScalar radius,
-                               GrColor umbraColor, GrColor penumbraColor, bool transparent);
+    GrAmbientShadowTessellator(const SkPath& path, SkScalar radius, GrColor umbraColor,
+                               GrColor penumbraColor, bool transparent);
 
     int      vertexCount() { return fPositions.count(); }
     SkPoint* positions() { return fPositions.begin(); }
@@ -34,14 +36,12 @@ public:
 
 private:
     void handleLine(const SkPoint& p);
-    void handleLine(const SkMatrix& m, SkPoint p);
 
     void handleQuad(const SkPoint pts[3]);
-    void handleQuad(const SkMatrix& m, SkPoint pts[3]);
 
-    void handleCubic(const SkMatrix& m, SkPoint pts[4]);
+    void handleCubic(SkPoint pts[4]);
 
-    void handleConic(const SkMatrix& m, SkPoint pts[3], SkScalar w);
+    void handleConic(SkPoint pts[3], SkScalar w);
 
     void addArc(const SkVector& nextNormal);
     void finishArcAndAddEdge(const SkVector& nextPoint, const SkVector& nextNormal);
@@ -69,4 +69,65 @@ private:
     SkTDArray<SkPoint>  fPointBuffer;
 };
 
+/**
+ * This class generates an spot shadow for a path by walking the transformed path, further 
+ * transforming by the scale and translation, and outsetting and insetting by a radius.
+ * The center will be clipped against the original path unless transparent is true.
+ */
+class GrSpotShadowTessellator {
+public:
+    GrSpotShadowTessellator(const SkPath& path, SkScalar scale, const SkVector& translate,
+                            SkScalar radius, GrColor umbraColor, GrColor penumbraColor,
+                            bool transparent);
+
+    int      vertexCount() { return fPositions.count(); }
+    SkPoint* positions() { return fPositions.begin(); }
+    GrColor* colors() { return fColors.begin(); }
+    int      indexCount() { return fIndices.count(); }
+    uint16_t* indices() { return fIndices.begin(); }
+
+private:
+    void computeClipBounds(const SkPath& path);
+
+    void handleLine(const SkPoint& p);
+    void handleLine(SkScalar scale, const SkVector& xlate, SkPoint p);
+
+    void handleQuad(const SkPoint pts[3]);
+    void handleQuad(SkScalar scale, const SkVector& xlate, SkPoint pts[3]);
+
+    void handleCubic(SkScalar scale, const SkVector& xlate, SkPoint pts[4]);
+
+    void handleConic(SkScalar scale, const SkVector& xlate, SkPoint pts[3], SkScalar w);
+
+    void mapPoints(SkScalar scale, const SkVector& xlate, SkPoint* pts, int count);
+    void addInnerPoint(const SkPoint& pathPoint, GrColor umbraColor, SkScalar radiusSqd);
+    void addArc(const SkVector& nextNormal);
+    void finishArcAndAddEdge(const SkVector& nextPoint, const SkVector& nextNormal);
+    void addEdge(const SkVector& nextPoint, const SkVector& nextNormal);
+
+    SkScalar            fRadius;
+    GrColor             fUmbraColor;
+    GrColor             fPenumbraColor;
+
+    SkTDArray<SkPoint>  fPositions;
+    SkTDArray<GrColor>  fColors;
+    SkTDArray<uint16_t> fIndices;
+
+    int                 fPrevInnerIndex;
+    SkPoint             fPrevPoint;
+    SkVector            fPrevNormal;
+    int                 fFirstVertex;
+    SkPoint             fFirstPoint;
+    SkVector            fFirstNormal;
+    SkScalar            fDirection;
+
+    SkPoint             fCentroid;
+    SkTDArray<SkPoint>  fClipPolygon;
+
+    // first three points
+    SkTDArray<SkPoint>  fInitPoints;
+    // temporary buffer
+    SkTDArray<SkPoint>  fPointBuffer;
+};
+
 #endif