Some improvements to reduce the number of pixels touched in generating alpha clip masks
Review URL: https://codereview.appspot.com/6828043

git-svn-id: http://skia.googlecode.com/svn/trunk@6329 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/include/core/SkClipStack.h b/include/core/SkClipStack.h
index a67e0a5..79fd2c9 100644
--- a/include/core/SkClipStack.h
+++ b/include/core/SkClipStack.h
@@ -134,6 +134,11 @@
                      fDoAA(false) {}
             friend bool operator==(const Clip& a, const Clip& b);
             friend bool operator!=(const Clip& a, const Clip& b);
+            /**
+             * Gets the bounds of the clip element, either the rect or path bounds.
+             */
+            const SkRect& getBounds() const;
+
             const SkRect*   fRect;  // if non-null, this is a rect clip
             const SkPath*   fPath;  // if non-null, this is a path clip
             SkRegion::Op    fOp;
diff --git a/include/core/SkMatrix.h b/include/core/SkMatrix.h
index 980bcaa..2d3786c 100644
--- a/include/core/SkMatrix.h
+++ b/include/core/SkMatrix.h
@@ -173,6 +173,8 @@
     /** Set the matrix to translate by (dx, dy).
     */
     void setTranslate(SkScalar dx, SkScalar dy);
+    void setTranslate(const SkVector& v) { this->setTranslate(v.fX, v.fY); }
+
     /** Set the matrix to scale by sx and sy, with a pivot point at (px, py).
         The pivot point is the coordinate that should remain unchanged by the
         specified transformation.
diff --git a/include/core/SkRandom.h b/include/core/SkRandom.h
index 0f9b9aa..4731bbe 100644
--- a/include/core/SkRandom.h
+++ b/include/core/SkRandom.h
@@ -84,7 +84,7 @@
         in the range [min..max).
     */
     SkScalar nextRangeScalar(SkScalar min, SkScalar max) {
-        return SkScalarMul(this->nextSScalar1(), (max - min)) + min;
+        return SkScalarMul(this->nextUScalar1(), (max - min)) + min;
     }
 
     /** Return the next pseudo random number expressed as a SkScalar
diff --git a/include/core/SkRect.h b/include/core/SkRect.h
index 7210bf9..f5e8c3c 100644
--- a/include/core/SkRect.h
+++ b/include/core/SkRect.h
@@ -366,6 +366,15 @@
         return r;
     }
 
+    static SkRect SK_WARN_UNUSED_RESULT MakeFromIRect(const SkIRect& irect) {
+        SkRect r;
+        r.set(SkIntToScalar(irect.fLeft),
+              SkIntToScalar(irect.fTop),
+              SkIntToScalar(irect.fRight),
+              SkIntToScalar(irect.fBottom));
+        return r;
+    }
+
     /**
      *  Return true if the rectangle's width or height are <= 0
      */
diff --git a/src/core/SkClipStack.cpp b/src/core/SkClipStack.cpp
index 0f2d632..a5b591e 100644
--- a/src/core/SkClipStack.cpp
+++ b/src/core/SkClipStack.cpp
@@ -724,6 +724,17 @@
     return !(a == b);
 }
 
+const SkRect& SkClipStack::Iter::Clip::getBounds() const {
+    if (NULL != fRect) {
+        return *fRect;
+    } else if (NULL != fPath) {
+        return fPath->getBounds();
+    } else {
+        static const SkRect kEmpty = {0, 0, 0, 0};
+        return kEmpty;
+    }
+}
+
 SkClipStack::Iter::Iter(const SkClipStack& stack, IterStart startLoc)
     : fStack(&stack) {
     this->reset(stack, startLoc);
diff --git a/src/gpu/GrClipMaskManager.cpp b/src/gpu/GrClipMaskManager.cpp
index 2be92dc..606b90e 100644
--- a/src/gpu/GrClipMaskManager.cpp
+++ b/src/gpu/GrClipMaskManager.cpp
@@ -7,6 +7,7 @@
  */
 
 #include "GrClipMaskManager.h"
+#include "effects/GrTextureDomainEffect.h"
 #include "GrGpu.h"
 #include "GrRenderTarget.h"
 #include "GrStencilBuffer.h"
@@ -42,7 +43,13 @@
     mat.preConcat(drawState->getViewMatrix());
 
     drawState->stage(kMaskStage)->reset();
-    drawState->createTextureEffect(kMaskStage, result, mat);
+
+    SkIRect domainTexels = SkIRect::MakeWH(devBound.width(), devBound.height());
+    drawState->stage(kMaskStage)->setEffect(
+        GrTextureDomainEffect::Create(result,
+                                      mat,
+                                      GrTextureDomainEffect::MakeTexelDomain(result, domainTexels),
+                                      GrTextureDomainEffect::kDecal_WrapMode))->unref();
 }
 
 bool path_needs_SW_renderer(GrContext* context,
@@ -152,8 +159,7 @@
     GrIRect devClipBounds;
     bool isIntersectionOfRects = false;
 
-    clipDataIn->getConservativeBounds(rt, &devClipBounds,
-                                      &isIntersectionOfRects);
+    clipDataIn->getConservativeBounds(rt, &devClipBounds, &isIntersectionOfRects);
     if (devClipBounds.isEmpty()) {
         return false;
     }
@@ -483,25 +489,31 @@
     return true;
 }
 
-void GrClipMaskManager::drawTexture(GrTexture* target,
-                                    GrTexture* texture) {
+void GrClipMaskManager::mergeMask(GrTexture* dstMask,
+                                  GrTexture* srcMask,
+                                  SkRegion::Op op,
+                                  const GrIRect& dstBound,
+                                  const GrIRect& srcBound) {
     GrDrawState* drawState = fGpu->drawState();
     GrAssert(NULL != drawState);
+    SkMatrix oldMatrix = drawState->getViewMatrix();
+    drawState->viewMatrix()->reset();
 
-    // no AA here since it is encoded in the texture
-    drawState->setRenderTarget(target->asRenderTarget());
+    drawState->setRenderTarget(dstMask->asRenderTarget());
 
+    setup_boolean_blendcoeffs(drawState, op);
+    
     SkMatrix sampleM;
-    sampleM.setIDiv(texture->width(), texture->height());
-
-    drawState->createTextureEffect(0, texture, sampleM);
-
-    GrRect rect = GrRect::MakeWH(SkIntToScalar(target->width()),
-                                 SkIntToScalar(target->height()));
-
-    fGpu->drawSimpleRect(rect, NULL);
+    sampleM.setIDiv(srcMask->width(), srcMask->height());
+    drawState->stage(0)->setEffect(
+        GrTextureDomainEffect::Create(srcMask,
+                                      sampleM,
+                                      GrTextureDomainEffect::MakeTexelDomain(srcMask, srcBound),
+                                      GrTextureDomainEffect::kDecal_WrapMode))->unref();
+    fGpu->drawSimpleRect(SkRect::MakeFromIRect(dstBound), NULL);
 
     drawState->disableStage(0);
+    drawState->setViewMatrix(oldMatrix);
 }
 
 // get a texture to act as a temporary buffer for AA clip boolean operations
@@ -599,14 +611,18 @@
 
     GrDrawTarget::AutoGeometryPush agp(fGpu);
 
-    if (0 != devResultBounds->fTop || 0 != devResultBounds->fLeft ||
-        0 != clipDataIn.fOrigin.fX || 0 != clipDataIn.fOrigin.fY) {
-        // if we were able to trim down the size of the mask we need to
-        // offset the paths & rects that will be used to compute it
-        drawState->viewMatrix()->setTranslate(
-                SkIntToScalar(-devResultBounds->fLeft-clipDataIn.fOrigin.fX),
-                SkIntToScalar(-devResultBounds->fTop-clipDataIn.fOrigin.fY));
-    }
+    // The mask we generate is translated so that its upper-left corner is at devResultBounds
+    // upper-left corner in device space.
+    GrIRect maskResultBounds = GrIRect::MakeWH(devResultBounds->width(), devResultBounds->height());
+
+    // Set the matrix so that rendered clip elements are transformed from the space of the clip
+    // stack to the alpha-mask. This accounts for both translation due to the clip-origin and the
+    // placement of the mask within the device.
+    SkVector clipToMaskOffset = {
+        SkIntToScalar(-devResultBounds->fLeft - clipDataIn.fOrigin.fX),
+        SkIntToScalar(-devResultBounds->fTop - clipDataIn.fOrigin.fY)
+    };
+    drawState->viewMatrix()->setTranslate(clipToMaskOffset);
 
     bool clearToInside;
     SkRegion::Op firstOp = SkRegion::kReplace_Op; // suppress warning
@@ -618,10 +634,12 @@
                                                               &clearToInside,
                                                               &firstOp,
                                                               clipDataIn);
-
-    fGpu->clear(NULL,
+    // The scratch texture that we are drawing into can be substantially larger than the mask. Only
+    // clear the part that we care about.
+    fGpu->clear(&maskResultBounds,
                 clearToInside ? 0xffffffff : 0x00000000,
                 accum->asRenderTarget());
+    bool accumClearedToZero = !clearToInside;
 
     GrAutoScratchTexture temp;
     bool first = true;
@@ -636,8 +654,10 @@
 
         if (SkRegion::kReplace_Op == op) {
             // clear the accumulator and draw the new object directly into it
-            fGpu->clear(NULL, 0x00000000, accum->asRenderTarget());
-
+            if (!accumClearedToZero) {
+                fGpu->clear(&maskResultBounds, 0x00000000, accum->asRenderTarget());
+            }
+            
             setup_boolean_blendcoeffs(drawState, op);
             this->drawClipShape(accum, clip, *devResultBounds);
 
@@ -655,40 +675,31 @@
                 return false;
             }
 
+            // this is the bounds of the clip element in the space of the alpha-mask. The temporary
+            // mask buffer can be substantially larger than the actually clip stack element. We
+            // touch the minimum number of pixels necessary and use decal mode to combine it with
+            // the accumulator
+            GrRect elementMaskBounds = clip->getBounds();
+            elementMaskBounds.offset(clipToMaskOffset);
+            GrIRect elementMaskIBounds;
+            elementMaskBounds.roundOut(&elementMaskIBounds);
+
             // clear the temp target & draw into it
-            fGpu->clear(NULL, 0x00000000, temp.texture()->asRenderTarget());
+            fGpu->clear(&elementMaskIBounds, 0x00000000, temp.texture()->asRenderTarget());
 
             setup_boolean_blendcoeffs(drawState, SkRegion::kReplace_Op);
-            this->drawClipShape(temp.texture(), clip, *devResultBounds);
-
-            // TODO: rather than adding these two translations here
-            // compute the bounding box needed to render the texture
-            // into temp
-            if (0 != devResultBounds->fTop || 0 != devResultBounds->fLeft ||
-                0 != clipDataIn.fOrigin.fX || 0 != clipDataIn.fOrigin.fY) {
-                // In order for the merge of the temp clip into the accumulator
-                // to work we need to disable the translation
-                drawState->viewMatrix()->reset();
-            }
+            this->drawClipShape(temp.texture(), clip, elementMaskIBounds);
 
             // Now draw into the accumulator using the real operation
             // and the temp buffer as a texture
-            setup_boolean_blendcoeffs(drawState, op);
-            this->drawTexture(accum, temp.texture());
-
-            if (0 != devResultBounds->fTop || 0 != devResultBounds->fLeft ||
-                0 != clipDataIn.fOrigin.fX || 0 != clipDataIn.fOrigin.fY) {
-                drawState->viewMatrix()->setTranslate(
-                  SkIntToScalar(-devResultBounds->fLeft-clipDataIn.fOrigin.fX),
-                  SkIntToScalar(-devResultBounds->fTop-clipDataIn.fOrigin.fY));
-            }
-
+            this->mergeMask(accum, temp.texture(), op, maskResultBounds, elementMaskIBounds);
         } else {
             // all the remaining ops can just be directly draw into
             // the accumulation buffer
             setup_boolean_blendcoeffs(drawState, op);
             this->drawClipShape(accum, clip, *devResultBounds);
         }
+        accumClearedToZero = false;
     }
 
     *result = accum;
diff --git a/src/gpu/GrClipMaskManager.h b/src/gpu/GrClipMaskManager.h
index d23a525..89b1ef8 100644
--- a/src/gpu/GrClipMaskManager.h
+++ b/src/gpu/GrClipMaskManager.h
@@ -128,8 +128,11 @@
                        const SkClipStack::Iter::Clip* clip,
                        const GrIRect& resultBounds);
 
-    void drawTexture(GrTexture* target,
-                     GrTexture* texture);
+    void mergeMask(GrTexture* dstMask,
+                   GrTexture* srcMask,
+                   SkRegion::Op op,
+                   const GrIRect& dstBound,
+                   const GrIRect& srcBound);
 
     void getTemp(const GrIRect& bounds, GrAutoScratchTexture* temp);
 
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index 14eeace..2d79991 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -1435,7 +1435,11 @@
             top = bottom = SkScalarHalf(paintRect.top() + paintRect.bottom());
         }
         textureDomain.setLTRB(left, top, right, bottom);
-        effect.reset(SkNEW_ARGS(GrTextureDomainEffect, (texture, textureDomain, params)));
+        effect.reset(GrTextureDomainEffect::Create(texture,
+                                                   SkMatrix::I(),
+                                                   textureDomain,
+                                                   GrTextureDomainEffect::kClamp_WrapMode,
+                                                   params.isBilerp()));
     } else {
         effect.reset(SkNEW_ARGS(GrSingleTextureEffect, (texture, params)));
     }
diff --git a/src/gpu/effects/GrTextureDomainEffect.cpp b/src/gpu/effects/GrTextureDomainEffect.cpp
index 823b072..e500fad 100644
--- a/src/gpu/effects/GrTextureDomainEffect.cpp
+++ b/src/gpu/effects/GrTextureDomainEffect.cpp
@@ -43,28 +43,40 @@
 }
 
 void GrGLTextureDomainEffect::emitCode(GrGLShaderBuilder* builder,
-                                       const GrEffectStage&,
+                                       const GrEffectStage& stage,
                                        EffectKey key,
                                        const char* vertexCoords,
                                        const char* outputColor,
                                        const char* inputColor,
                                        const TextureSamplerArray& samplers) {
+    const GrTextureDomainEffect& effect =
+        static_cast<const GrTextureDomainEffect&>(*stage.getEffect());
+
     const char* coords;
     fEffectMatrix.emitCodeMakeFSCoords2D(builder, key, vertexCoords, &coords);
+    const char* domain;
     fNameUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
-                                   kVec4f_GrSLType, "TexDom");
+                                    kVec4f_GrSLType, "TexDom", &domain);
+    if (GrTextureDomainEffect::kClamp_WrapMode == effect.wrapMode()) {
 
-    builder->fFSCode.appendf("\tvec2 clampCoord = clamp(%s, %s.xy, %s.zw);\n",
-                           coords,
-                           builder->getUniformCStr(fNameUni),
-                           builder->getUniformCStr(fNameUni));
+        builder->fFSCode.appendf("\tvec2 clampCoord = clamp(%s, %s.xy, %s.zw);\n",
+                               coords, domain, domain);
 
-    builder->fFSCode.appendf("\t%s = ", outputColor);
-    builder->appendTextureLookupAndModulate(&builder->fFSCode,
-                                            inputColor,
-                                            samplers[0],
-                                            "clampCoord");
-    builder->fFSCode.append(";\n");
+        builder->fFSCode.appendf("\t%s = ", outputColor);
+        builder->appendTextureLookupAndModulate(&builder->fFSCode,
+                                                inputColor,
+                                                samplers[0],
+                                                "clampCoord");
+        builder->fFSCode.append(";\n");
+    } else {
+        GrAssert(GrTextureDomainEffect::kDecal_WrapMode == effect.wrapMode());
+        builder->fFSCode.append("\tbvec4 outside;\n");
+        builder->fFSCode.appendf("\toutside.xy = lessThan(%s, %s.xy);\n", coords, domain);
+        builder->fFSCode.appendf("\toutside.zw = greaterThan(%s, %s.zw);\n", coords, domain);
+        builder->fFSCode.appendf("\t%s = any(outside) ? vec4(0.0, 0.0, 0.0, 0.0) : ", outputColor);
+        builder->appendTextureLookupAndModulate(&builder->fFSCode, inputColor, samplers[0], coords);
+        builder->fFSCode.append(";\n");
+    }
 }
 
 void GrGLTextureDomainEffect::setData(const GrGLUniformManager& uman, const GrEffectStage& stage) {
@@ -98,21 +110,41 @@
 GrGLEffect::EffectKey GrGLTextureDomainEffect::GenKey(const GrEffectStage& stage, const GrGLCaps&) {
     const GrTextureDomainEffect& effect =
         static_cast<const GrTextureDomainEffect&>(*stage.getEffect());
-    return GrGLEffectMatrix::GenKey(effect.getMatrix(), stage.getCoordChangeMatrix(), effect.texture(0));
+    EffectKey key = effect.wrapMode();
+    key <<= GrGLEffectMatrix::kKeyBits;
+    EffectKey matrixKey = GrGLEffectMatrix::GenKey(effect.getMatrix(),
+                                                   stage.getCoordChangeMatrix(),
+                                                   effect.texture(0));
+    return key | matrixKey;
 }
 
 
 ///////////////////////////////////////////////////////////////////////////////
 
-GrTextureDomainEffect::GrTextureDomainEffect(GrTexture* texture, const GrRect& domain)
-    : GrSingleTextureEffect(texture)
-    , fTextureDomain(domain) {
+GrEffect* GrTextureDomainEffect::Create(GrTexture* texture,
+                                        const SkMatrix& matrix,
+                                        const GrRect& domain,
+                                        WrapMode wrapMode,
+                                        bool bilerp) {
+    static const SkRect kFullRect = {0, 0, SK_Scalar1, SK_Scalar1};
+    if (kClamp_WrapMode == wrapMode && domain.contains(kFullRect)) {
+        return SkNEW_ARGS(GrSingleTextureEffect, (texture, matrix, bilerp));
+    } else {
+        SkRect clippedDomain;
+        // We don't currently handle domains that are empty or don't intersect the texture.
+        SkAssertResult(clippedDomain.intersect(kFullRect, domain));
+        return SkNEW_ARGS(GrTextureDomainEffect,
+                          (texture, matrix, clippedDomain, wrapMode, bilerp));
+    }
 }
 
 GrTextureDomainEffect::GrTextureDomainEffect(GrTexture* texture,
+                                             const SkMatrix& matrix,
                                              const GrRect& domain,
-                                             const GrTextureParams& params)
-    : GrSingleTextureEffect(texture, params)
+                                             WrapMode wrapMode,
+                                             bool bilerp)
+    : GrSingleTextureEffect(texture, matrix, bilerp)
+    , fWrapMode(wrapMode)
     , fTextureDomain(domain) {
 }
 
@@ -143,5 +175,7 @@
     domain.fRight = random->nextRangeScalar(domain.fLeft, SK_Scalar1);
     domain.fTop = random->nextUScalar1();
     domain.fBottom = random->nextRangeScalar(domain.fTop, SK_Scalar1);
-    return SkNEW_ARGS(GrTextureDomainEffect, (textures[texIdx], domain));
+    WrapMode wrapMode = random->nextBool() ? kClamp_WrapMode : kDecal_WrapMode;
+    const SkMatrix& matrix = GrEffectUnitTest::TestMatrix(random);
+    return GrTextureDomainEffect::Create(textures[texIdx], matrix, domain, wrapMode);
 }
diff --git a/src/gpu/effects/GrTextureDomainEffect.h b/src/gpu/effects/GrTextureDomainEffect.h
index 322f8d0..c1ce6d1 100644
--- a/src/gpu/effects/GrTextureDomainEffect.h
+++ b/src/gpu/effects/GrTextureDomainEffect.h
@@ -14,15 +14,31 @@
 class GrGLTextureDomainEffect;
 
 /**
- * Limits a texture's lookup coordinates to a domain.
+ * Limits a texture's lookup coordinates to a domain. Samples outside the domain are either clamped
+ * the edge of the domain or result in a vec4 of zeros. The domain is clipped to normalized texture
+ * coords ([0,1]x[0,1] square). Bilinear filtering can cause texels outside the domain to affect the
+ * read value unless the caller considers this when calculating the domain. TODO: This should be a
+ * helper that can assist an effect rather than effect unto itself.
  */
 class GrTextureDomainEffect : public GrSingleTextureEffect {
 
 public:
-    /** Uses default texture params (no filter, clamp) */
-    GrTextureDomainEffect(GrTexture*, const GrRect& domain);
+    /**
+     * If SkShader::kDecal_TileMode sticks then this enum could be replaced by SkShader::TileMode.
+     * We could also consider replacing/augmenting Decal mode with Border mode where the color
+     * outside of the domain is user-specifiable. Decal mode currently has a hard (non-lerped)
+     * transition between the border and the interior.
+     */
+    enum WrapMode {
+        kClamp_WrapMode,
+        kDecal_WrapMode,
+    };
 
-    GrTextureDomainEffect(GrTexture*, const GrRect& domain, const GrTextureParams& params);
+    static GrEffect* Create(GrTexture*,
+                            const SkMatrix&,
+                            const SkRect& domain,
+                            WrapMode,
+                            bool bilerp = false);
 
     virtual ~GrTextureDomainEffect();
 
@@ -33,13 +49,34 @@
     virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
     virtual bool isEqual(const GrEffect&) const SK_OVERRIDE;
 
-    const GrRect& domain() const { return fTextureDomain; }
+    const SkRect& domain() const { return fTextureDomain; }
+    WrapMode wrapMode() const { return fWrapMode; }
+
+    /* Computes a domain that bounds all the texels in texelRect. Note that with bilerp enabled
+       texels neighboring the domain may be read. */
+    static const SkRect MakeTexelDomain(const GrTexture* texture, const SkIRect& texelRect) {
+        SkScalar wInv = SK_Scalar1 / texture->width();
+        SkScalar hInv = SK_Scalar1 / texture->height();
+        SkRect result = {
+            texelRect.fLeft * wInv,
+            texelRect.fTop * hInv,
+            texelRect.fRight * wInv,
+            texelRect.fBottom * hInv
+        };
+        return result;
+    }
 
 protected:
-
-    GrRect fTextureDomain;
+    WrapMode fWrapMode;
+    SkRect   fTextureDomain;
 
 private:
+    GrTextureDomainEffect(GrTexture*,
+                          const SkMatrix&,
+                          const GrRect& domain,
+                          WrapMode,
+                          bool bilerp);
+
     GR_DECLARE_EFFECT_TEST;
 
     typedef GrSingleTextureEffect INHERITED;