Beef up GrContext::AutoMatrix to handle doing GrPaint matrix adjustments.

R=robertphillips@google.com
Review URL: https://codereview.appspot.com/6656047

git-svn-id: http://skia.googlecode.com/svn/trunk@5918 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/include/gpu/GrContext.h b/include/gpu/GrContext.h
index a193848..3158a03 100644
--- a/include/gpu/GrContext.h
+++ b/include/gpu/GrContext.h
@@ -537,7 +537,7 @@
      * @param height        height of rectangle to write in pixels.
      * @param config        the pixel config of the source buffer
      * @param buffer        memory to read the rectangle from.
-     * @param rowBytes      number of bytes bewtween consecutive rows. Zero means rows are tightly
+     * @param rowBytes      number of bytes between consecutive rows. Zero means rows are tightly
      *                      packed.
      * @param pixelOpsFlags see PixelOpsFlags enum above.
      */
@@ -556,7 +556,7 @@
      * @param height        height of rectangle to read in pixels.
      * @param config        the pixel config of the destination buffer
      * @param buffer        memory to read the rectangle into.
-     * @param rowBytes      number of bytes bewtween consecutive rows. Zero means rows are tightly
+     * @param rowBytes      number of bytes between consecutive rows. Zero means rows are tightly
      *                      packed.
      * @param pixelOpsFlags see PixelOpsFlags enum above.
      *
@@ -578,7 +578,7 @@
      * @param height        height of rectangle to write in pixels.
      * @param config        the pixel config of the source buffer
      * @param buffer        memory to read pixels from
-     * @param rowBytes      number of bytes bewtween consecutive rows. Zero
+     * @param rowBytes      number of bytes between consecutive rows. Zero
      *                      means rows are tightly packed.
      * @param pixelOpsFlags see PixelOpsFlags enum above.
      */
@@ -669,55 +669,104 @@
     };
 
     /**
-     *  Save/restore the view-matrix in the context.
+     * Save/restore the view-matrix in the context. It can optionally adjust a paint to account
+     * for a coordinate system change. Here is an example of how the paint param can be used:
+     *
+     * A GrPaint is setup with custom stages. The stages will have access to the pre-matrix source
+     * geometry positions when the draw is executed. Later on a decision is made to transform the
+     * geometry to device space on the CPU. The custom stages now need to know that the space in
+     * which the geometry will be specified has changed.
+     *
+     * Note that when restore is called (or in the destructor) the context's matrix will be
+     * restored. However, the paint will not be restored. The caller must make a copy of the
+     * paint if necessary. Hint: use SkTCopyOnFirstWrite if the AutoMatrix is conditionally
+     * initialized.
      */
     class AutoMatrix : GrNoncopyable {
     public:
-        enum InitialMatrix {
-            kPreserve_InitialMatrix,
-            kIdentity_InitialMatrix,
-        };
-
         AutoMatrix() : fContext(NULL) {}
 
-        AutoMatrix(GrContext* ctx, InitialMatrix initialState) : fContext(ctx) {
-            fMatrix = ctx->getMatrix();
-            switch (initialState) {
-                case kPreserve_InitialMatrix:
-                    break;
-                case kIdentity_InitialMatrix:
-                    ctx->setMatrix(GrMatrix::I());
-                    break;
-                default:
-                    GrCrash("Unexpected initial matrix state");
+        ~AutoMatrix() { this->restore(); }
+
+        /**
+         * Initializes by pre-concat'ing the context's current matrix with the preConcat param.
+         */
+        void setPreConcat(GrContext* context, const GrMatrix& preConcat, GrPaint* paint = NULL) {
+            GrAssert(NULL != context);
+
+            this->restore();
+            
+            fContext = context;
+            fMatrix = context->getMatrix();
+            this->preConcat(preConcat, paint);
+        }
+
+        /**
+         * Sets the context's matrix to identity. Returns false if the inverse matrix is required to
+         * update a paint but the matrix cannot be inverted.
+         */
+        bool setIdentity(GrContext* context, GrPaint* paint = NULL) {
+            GrAssert(NULL != context);
+
+            this->restore();
+            
+            if (NULL != paint) {
+                if (!paint->preConcatSamplerMatricesWithInverse(context->getMatrix())) {
+                    return false;
+                }
             }
+            fMatrix = context->getMatrix();
+            fContext = context;
+            context->setIdentityMatrix();
+            return true;
         }
 
-        AutoMatrix(GrContext* ctx, const GrMatrix& matrix) : fContext(ctx) {
-            fMatrix = ctx->getMatrix();
-            ctx->setMatrix(matrix);
+        /**
+         * Replaces the context's matrix with a new matrix. Returns false if the inverse matrix is
+         * required to update a paint but the matrix cannot be inverted.
+         */
+        bool set(GrContext* context, const GrMatrix& newMatrix, GrPaint* paint = NULL) {
+            if (NULL != paint) {
+                if (!this->setIdentity(context, paint)) {
+                    return false;
+                }
+                this->preConcat(newMatrix, paint);
+            } else {
+                this->restore();
+                fContext = context;
+                fMatrix = context->getMatrix();
+                context->setMatrix(newMatrix);
+            }
+            return true;
         }
 
-        void set(GrContext* ctx) {
+        /**
+         * If this has been initialized then the context's matrix will be further updated by
+         * pre-concat'ing the preConcat param. The matrix that will be restored remains unchanged.
+         * The paint is assumed to be relative to the context's matrix at the time this call is
+         * made, not the matrix at the time AutoMatrix was first initialized. In other words, this
+         * performs an incremental update of the paint.
+         */
+        void preConcat(const GrMatrix& preConcat, GrPaint* paint = NULL) {
+            if (NULL != paint) {
+                paint->preConcatSamplerMatrices(preConcat);
+            }
+            fContext->concatMatrix(preConcat);
+        }
+
+        /**
+         * Returns false if never initialized or the inverse matrix was required to update a paint
+         * but the matrix could not be inverted.
+         */
+        bool succeeded() const { return NULL != fContext; }
+
+        /**
+         * If this has been initialized then the context's original matrix is restored.
+         */
+        void restore() {
             if (NULL != fContext) {
                 fContext->setMatrix(fMatrix);
-            }
-            fMatrix = ctx->getMatrix();
-            fContext = ctx;
-        }
-
-        void set(GrContext* ctx, const GrMatrix& matrix) {
-            if (NULL != fContext) {
-                fContext->setMatrix(fMatrix);
-            }
-            fMatrix = ctx->getMatrix();
-            ctx->setMatrix(matrix);
-            fContext = ctx;
-        }
-
-        ~AutoMatrix() {
-            if (NULL != fContext) {
-                fContext->setMatrix(fMatrix);
+                fContext = NULL;
             }
         }
 
@@ -770,9 +819,12 @@
     public:
         AutoWideOpenIdentityDraw(GrContext* ctx, GrRenderTarget* rt)
             : fAutoClip(ctx, AutoClip::kWideOpen_InitialClip)
-            , fAutoRT(ctx, rt)
-            , fAutoMatrix(ctx, AutoMatrix::kIdentity_InitialMatrix) {
+            , fAutoRT(ctx, rt) {
+            fAutoMatrix.setIdentity(ctx);
+            // should never fail with no paint param.
+            GrAssert(fAutoMatrix.succeeded());
         }
+
     private:
         AutoClip fAutoClip;
         AutoRenderTarget fAutoRT;
diff --git a/include/gpu/GrPaint.h b/include/gpu/GrPaint.h
index 9f9403e..3c29662 100644
--- a/include/gpu/GrPaint.h
+++ b/include/gpu/GrPaint.h
@@ -189,9 +189,8 @@
     bool hasStage() const { return this->hasColorStage() || this->hasCoverageStage(); }
 
     /**
-     * Preconcats the matrix of all samplers in the mask with the inverse of a matrix. If the
-     * matrix inverse cannot be computed (and there is at least one enabled stage) then false is
-     * returned.
+     * Preconcats the matrix of all enabled stages with the inverse of a matrix. If the matrix
+     * inverse cannot be computed (and there is at least one enabled stage) then false is returned.
      */
     bool preConcatSamplerMatricesWithInverse(const GrMatrix& matrix) {
         GrMatrix inv;
@@ -219,6 +218,22 @@
         return true;
     }
 
+    /**
+     * Preconcats the matrix of all stages with a matrix.
+     */
+    void preConcatSamplerMatrices(const GrMatrix& matrix) {
+        for (int i = 0; i < kMaxColorStages; ++i) {
+            if (this->isColorStageEnabled(i)) {
+                fColorSamplers[i].preConcatMatrix(matrix);
+            }
+        }
+        for (int i = 0; i < kMaxCoverageStages; ++i) {
+            if (this->isCoverageStageEnabled(i)) {
+                fCoverageSamplers[i].preConcatMatrix(matrix);
+            }
+        }
+    }
+
     GrPaint& operator=(const GrPaint& paint) {
         fSrcBlendCoeff = paint.fSrcBlendCoeff;
         fDstBlendCoeff = paint.fDstBlendCoeff;
diff --git a/src/effects/SkBlendImageFilter.cpp b/src/effects/SkBlendImageFilter.cpp
index a1ad8ec..7933e27 100644
--- a/src/effects/SkBlendImageFilter.cpp
+++ b/src/effects/SkBlendImageFilter.cpp
@@ -196,7 +196,10 @@
 
     GrAutoScratchTexture ast(context, desc);
     GrTexture* dst = ast.detach();
-    GrContext::AutoMatrix avm(context, GrMatrix::I());
+
+    GrContext::AutoMatrix am;
+    am.setIdentity(context);
+
     GrContext::AutoRenderTarget art(context, dst->asRenderTarget());
     GrContext::AutoClip ac(context, rect);
 
diff --git a/src/effects/SkMorphologyImageFilter.cpp b/src/effects/SkMorphologyImageFilter.cpp
index e00d94a..64d22be 100644
--- a/src/effects/SkMorphologyImageFilter.cpp
+++ b/src/effects/SkMorphologyImageFilter.cpp
@@ -442,14 +442,19 @@
                             SkISize radius) {
     GrContext* context = srcTexture->getContext();
     srcTexture->ref();
-    GrContext::AutoMatrix avm(context, GrMatrix::I());
+
+    GrContext::AutoMatrix am;
+    am.setIdentity(context);
+
     GrContext::AutoClip acs(context, GrRect::MakeWH(SkIntToScalar(srcTexture->width()),
                                                     SkIntToScalar(srcTexture->height())));
+
     GrTextureDesc desc;
     desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit;
     desc.fWidth = SkScalarCeilToInt(rect.width());
     desc.fHeight = SkScalarCeilToInt(rect.height());
     desc.fConfig = kRGBA_8888_GrPixelConfig;
+
     if (radius.fWidth > 0) {
         GrAutoScratchTexture ast(context, desc);
         GrContext::AutoRenderTarget art(context, ast.texture()->asRenderTarget());
diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp
index 8997280..ee3a4e1 100644
--- a/src/gpu/GrContext.cpp
+++ b/src/gpu/GrContext.cpp
@@ -603,12 +603,10 @@
         }
         inverse.mapRect(&r);
     } else {
-        if (paint->hasStage()) {
-            if (!paint.writable()->preConcatSamplerMatricesWithInverse(fDrawState->getViewMatrix())) {
-                GrPrintf("Could not invert matrix\n");
-            }
+        if (!am.setIdentity(this, paint.writable())) {
+            GrPrintf("Could not invert matrix\n");
+            return;
         }
-        am.set(this, GrMatrix::I());
     }
     // by definition this fills the entire clip, no need for AA
     if (paint->isAntiAlias()) {
@@ -1780,7 +1778,9 @@
 
     AutoRenderTarget art(this);
 
-    AutoMatrix avm(this, GrMatrix::I());
+    AutoMatrix am;
+    am.setIdentity(this);
+
     SkIRect clearRect;
     int scaleFactorX, radiusX;
     int scaleFactorY, radiusY;
diff --git a/src/gpu/GrTextContext.cpp b/src/gpu/GrTextContext.cpp
index 042e030..6846721 100644
--- a/src/gpu/GrTextContext.cpp
+++ b/src/gpu/GrTextContext.cpp
@@ -206,12 +206,13 @@
             glyph->fPath = path;
         }
 
-        GrContext::AutoMatrix am(fContext, GrContext::AutoMatrix::kPreserve_InitialMatrix);
+        GrContext::AutoMatrix am;
         GrMatrix translate;
         translate.setTranslate(GrFixedToScalar(vx - GrIntToFixed(glyph->fBounds.fLeft)),
                                GrFixedToScalar(vy - GrIntToFixed(glyph->fBounds.fTop)));
-        fContext->concatMatrix(translate);
-        fContext->drawPath(fPaint, *glyph->fPath, kWinding_GrPathFill);
+        GrPaint tmpPaint(fPaint);
+        am.setPreConcat(fContext, translate, &tmpPaint);
+        fContext->drawPath(tmpPaint, *glyph->fPath, kWinding_GrPathFill);
         return;
     }
 
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index d94a8dd..50cc12b 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -26,13 +26,13 @@
 
 #if 0
     extern bool (*gShouldDrawProc)();
-    #define CHECK_SHOULD_DRAW(draw)                             \
+    #define CHECK_SHOULD_DRAW(draw, forceI)                     \
         do {                                                    \
             if (gShouldDrawProc && !gShouldDrawProc()) return;  \
-            this->prepareDraw(draw);                            \
+            this->prepareDraw(draw, forceI);                    \
         } while (0)
 #else
-    #define CHECK_SHOULD_DRAW(draw) this->prepareDraw(draw)
+    #define CHECK_SHOULD_DRAW(draw, forceI) this->prepareDraw(draw, forceI)
 #endif
 
 // we use the same texture slot on GrPaint for bitmaps and shaders
@@ -407,14 +407,18 @@
 
 // call this every draw call, to ensure that the context reflects our state,
 // and not the state from some other canvas/device
-void SkGpuDevice::prepareDraw(const SkDraw& draw) {
+void SkGpuDevice::prepareDraw(const SkDraw& draw, bool forceIdentity) {
     GrAssert(NULL != fClipData.fClipStack);
 
     fContext->setRenderTarget(fRenderTarget);
 
     SkASSERT(draw.fClipStack && draw.fClipStack == fClipData.fClipStack);
 
-    fContext->setMatrix(*draw.fMatrix);
+    if (forceIdentity) {
+        fContext->setIdentityMatrix();
+    } else {
+        fContext->setMatrix(*draw.fMatrix);
+    }
     fClipData.fOrigin = this->getOrigin();
 
 #ifdef SK_DEBUG
@@ -625,7 +629,7 @@
 }
 
 void SkGpuDevice::drawPaint(const SkDraw& draw, const SkPaint& paint) {
-    CHECK_SHOULD_DRAW(draw);
+    CHECK_SHOULD_DRAW(draw, false);
 
     GrPaint grPaint;
     SkAutoCachedTexture textures[GrPaint::kMaxColorStages];
@@ -649,7 +653,7 @@
 
 void SkGpuDevice::drawPoints(const SkDraw& draw, SkCanvas::PointMode mode,
                              size_t count, const SkPoint pts[], const SkPaint& paint) {
-    CHECK_SHOULD_DRAW(draw);
+    CHECK_SHOULD_DRAW(draw, false);
 
     SkScalar width = paint.getStrokeWidth();
     if (width < 0) {
@@ -688,7 +692,7 @@
 void SkGpuDevice::drawRect(const SkDraw& draw, const SkRect& rect,
                            const SkPaint& paint) {
     CHECK_FOR_NODRAW_ANNOTATION(paint);
-    CHECK_SHOULD_DRAW(draw);
+    CHECK_SHOULD_DRAW(draw, false);
 
     bool doStroke = paint.getStyle() != SkPaint::kFill_Style;
     SkScalar width = paint.getStrokeWidth();
@@ -837,21 +841,12 @@
 
     SkAutoTUnref<GrTexture> blurTexture;
 
-    GrMatrix origMatrix = context->getMatrix();
-
-    // We pass kPreserve here. We will replace the current matrix below.
-    GrContext::AutoMatrix avm(context, GrContext::AutoMatrix::kPreserve_InitialMatrix);
-
     {
         GrContext::AutoRenderTarget art(context, pathTexture->asRenderTarget());
         GrContext::AutoClip ac(context, srcRect);
 
         context->clear(NULL, 0);
 
-        // Draw hard shadow to pathTexture with path top-left at origin 0,0.
-        GrMatrix translate;
-        translate.setTranslate(offset.fX, offset.fY);
-
         GrPaint tempPaint;
         if (grp->isAntiAlias()) {
             tempPaint.setAntiAlias(true);
@@ -860,14 +855,17 @@
             // to properly blend partially covered pixels. This means the AA
             // code path may not be taken. So we use a dst blend coeff of ISA. We
             // could special case AA draws to a dst surface with known alpha=0 to
-            // use a zero dst coeff when dual source blending isn't available.
+            // use a zero dst coeff when dual source blending isn't available.f
             tempPaint.setBlendFunc(kOne_GrBlendCoeff, kISC_GrBlendCoeff);
         }
-        context->setMatrix(translate);
-        context->drawPath(tempPaint, devPath, pathFillType);
 
-        // switch to device coord drawing when going back to the main RT.
-        context->setIdentityMatrix();
+        GrContext::AutoMatrix am;
+
+        // Draw hard shadow to pathTexture with path top-left at origin using tempPaint.
+        GrMatrix translate;
+        translate.setTranslate(offset.fX, offset.fY);
+        am.set(context, translate);
+        context->drawPath(tempPaint, devPath, pathFillType);
 
         // If we're doing a normal blur, we can clobber the pathTexture in the
         // gaussianBlur.  Otherwise, we need to save it for later compositing.
@@ -876,6 +874,7 @@
                                                 srcRect, sigma, sigma));
 
         if (!isNormalBlur) {
+            context->setIdentityMatrix();
             GrPaint paint;
             paint.reset();
             paint.colorSampler(0)->matrix()->setIDiv(pathTexture->width(),
@@ -900,10 +899,11 @@
         }
     }
 
-    if (!grp->preConcatSamplerMatricesWithInverse(origMatrix)) {
+    GrContext::AutoMatrix am;
+    if (!am.setIdentity(context, grp)) {
         return false;
     }
-
+   
     static const int MASK_IDX = GrPaint::kMaxCoverageStages - 1;
     // we assume the last mask index is available for use
     GrAssert(!grp->isCoverageStageEnabled(MASK_IDX));
@@ -943,13 +943,9 @@
     }
 
     // we now have a device-aligned 8bit mask in dstM, ready to be drawn using
-    // the current clip (and identity matrix) and grpaint settings
-
-    if (!grp->preConcatSamplerMatricesWithInverse(context->getMatrix())) {
-        return false;
-    }
-
-    GrContext::AutoMatrix avm(context, GrMatrix::I());
+    // the current clip (and identity matrix) and GrPaint settings
+    GrContext::AutoMatrix am;
+    am.setIdentity(context, grp);
 
     GrTextureDesc desc;
     desc.fWidth = dstM.fBounds.width();
@@ -993,7 +989,7 @@
                            const SkPaint& paint, const SkMatrix* prePathMatrix,
                            bool pathIsMutable) {
     CHECK_FOR_NODRAW_ANNOTATION(paint);
-    CHECK_SHOULD_DRAW(draw);
+    CHECK_SHOULD_DRAW(draw, false);
 
     bool             doFill = true;
 
@@ -1202,7 +1198,7 @@
                                    const SkRect* srcRectPtr,
                                    const SkMatrix& m,
                                    const SkPaint& paint) {
-    CHECK_SHOULD_DRAW(draw);
+    CHECK_SHOULD_DRAW(draw, false);
 
     SkRect srcRect;
     if (NULL == srcRectPtr) {
@@ -1465,7 +1461,8 @@
                         const GrRect& rect,
                         GrCustomStage* stage) {
     SkASSERT(srcTexture && srcTexture->getContext() == context);
-    GrContext::AutoMatrix avm(context, GrMatrix::I());
+    GrContext::AutoMatrix am;
+    am.setIdentity(context);
     GrContext::AutoRenderTarget art(context, dstTexture->asRenderTarget());
     GrContext::AutoClip acs(context, rect);
 
@@ -1504,8 +1501,9 @@
 }
 
 void SkGpuDevice::drawSprite(const SkDraw& draw, const SkBitmap& bitmap,
-                            int left, int top, const SkPaint& paint) {
-    CHECK_SHOULD_DRAW(draw);
+                             int left, int top, const SkPaint& paint) {
+    // drawSprite is defined to be in device coords.
+    CHECK_SHOULD_DRAW(draw, true);
 
     SkAutoLockPixels alp(bitmap, !bitmap.getTexture());
     if (!bitmap.getTexture() && !bitmap.readyToDraw()) {
@@ -1521,8 +1519,6 @@
         return;
     }
 
-    GrContext::AutoMatrix avm(fContext, GrMatrix::I());
-
     GrSamplerState* sampler = grPaint.colorSampler(kBitmapTextureIdx);
 
     GrTexture* texture;
@@ -1584,7 +1580,7 @@
 }
 
 void SkGpuDevice::drawDevice(const SkDraw& draw, SkDevice* device,
-                            int x, int y, const SkPaint& paint) {
+                             int x, int y, const SkPaint& paint) {
     // clear of the source device must occur before CHECK_SHOULD_DRAW
     SkGpuDevice* dev = static_cast<SkGpuDevice*>(device);
     if (dev->fNeedClear) {
@@ -1592,7 +1588,8 @@
         dev->clear(0x0);
     }
 
-    CHECK_SHOULD_DRAW(draw);
+    // drawDevice is defined to be in device coords.
+    CHECK_SHOULD_DRAW(draw, true);
 
     GrPaint grPaint;
     SkAutoCachedTexture colorLutTexture;
@@ -1622,7 +1619,6 @@
     int w = bm.width();
     int h = bm.height();
 
-    GrContext::AutoMatrix avm(fContext, GrMatrix::I());
     GrRect dstRect = GrRect::MakeXYWH(GrIntToScalar(x),
                                       GrIntToScalar(y),
                                       GrIntToScalar(w),
@@ -1691,7 +1687,7 @@
                               SkXfermode* xmode,
                               const uint16_t indices[], int indexCount,
                               const SkPaint& paint) {
-    CHECK_SHOULD_DRAW(draw);
+    CHECK_SHOULD_DRAW(draw, false);
 
     GrPaint grPaint;
     SkAutoCachedTexture textures[GrPaint::kMaxColorStages];
@@ -1800,7 +1796,7 @@
 void SkGpuDevice::drawText(const SkDraw& draw, const void* text,
                           size_t byteLength, SkScalar x, SkScalar y,
                           const SkPaint& paint) {
-    CHECK_SHOULD_DRAW(draw);
+    CHECK_SHOULD_DRAW(draw, false);
 
     if (fContext->getMatrix().hasPerspective()) {
         // this guy will just call our drawPath()
@@ -1827,7 +1823,7 @@
                              size_t byteLength, const SkScalar pos[],
                              SkScalar constY, int scalarsPerPos,
                              const SkPaint& paint) {
-    CHECK_SHOULD_DRAW(draw);
+    CHECK_SHOULD_DRAW(draw, false);
 
     if (fContext->getMatrix().hasPerspective()) {
         // this guy will just call our drawPath()
@@ -1855,7 +1851,7 @@
 void SkGpuDevice::drawTextOnPath(const SkDraw& draw, const void* text,
                                 size_t len, const SkPath& path,
                                 const SkMatrix* m, const SkPaint& paint) {
-    CHECK_SHOULD_DRAW(draw);
+    CHECK_SHOULD_DRAW(draw, false);
 
     SkASSERT(draw.fDevice == this);
     draw.drawTextOnPath((const char*)text, len, path, m, paint);