SkTCopyOnFirstWrite

R=reed@google.com
Review URL: https://codereview.appspot.com/6650047

git-svn-id: http://skia.googlecode.com/svn/trunk@5905 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/include/core/SkTLazy.h b/include/core/SkTLazy.h
index cf2d943..cd1e8f2 100644
--- a/include/core/SkTLazy.h
+++ b/include/core/SkTLazy.h
@@ -88,5 +88,59 @@
     char fStorage[sizeof(T)];
 };
 
+/**
+ * A helper built on top of SkTLazy to do copy-on-first-write. The object is initialized
+ * with a const pointer but provides a non-const pointer accessor. The first time the
+ * accessor is called (if ever) the object is cloned.
+ *
+ * In the following example at most one copy of constThing is made:
+ *
+ * SkTCopyOnFirstWrite<Thing> thing(&constThing);
+ * ...
+ * function_that_takes_a_const_thing_ptr(thing); // constThing is passed
+ * ...
+ * if (need_to_modify_thing()) {
+ *    thing.writable()->modifyMe(); // makes a copy of constThing
+ * }
+ * ...
+ * x = thing->readSomething();
+ * ...
+ * if (need_to_modify_thing_now()) {
+ *    thing.writable()->changeMe(); // makes a copy of constThing if we didn't call modifyMe()
+ * }
+ *
+ * consume_a_thing(thing); // could be constThing or a modified copy.
+ */
+template <typename T>
+class SkTCopyOnFirstWrite {
+public:
+    SkTCopyOnFirstWrite(const T& initial) : fObj(&initial) {}
+
+    /**
+     * Returns a writable T*. The first time this is called the initial object is cloned.
+     */
+    T* writable() {
+        if (!fLazy.isValid()) {
+            fLazy.set(*fObj);
+            fObj = fLazy.get();
+        }
+        return const_cast<T*>(fObj);
+    }
+
+    /**
+     * Operators for treating this as though it were a const pointer.
+     */
+
+    const T *operator->() const { return fObj; }
+
+    operator const T*() const { return fObj; }
+
+    const T& operator *() const { return *fObj; }
+
+private:
+    const T*    fObj;
+    SkTLazy<T>  fLazy;
+};
+
 #endif
 
diff --git a/src/core/SkBlitter.cpp b/src/core/SkBlitter.cpp
index 322f91d..cdb2b62 100644
--- a/src/core/SkBlitter.cpp
+++ b/src/core/SkBlitter.cpp
@@ -847,17 +847,13 @@
     SkXfermode* mode = origPaint.getXfermode();
     Sk3DShader* shader3D = NULL;
 
-    SkTLazy<SkPaint> lazyPaint;
-    // we promise not to mutate paint unless we know we've reassigned it from
-    // lazyPaint
-    SkPaint* paint = const_cast<SkPaint*>(&origPaint);
+    SkTCopyOnFirstWrite<SkPaint> paint(origPaint);
 
     if (origPaint.getMaskFilter() != NULL &&
             origPaint.getMaskFilter()->getFormat() == SkMask::k3D_Format) {
         shader3D = SkNEW_ARGS(Sk3DShader, (shader));
         // we know we haven't initialized lazyPaint yet, so just do it
-        paint = lazyPaint.set(origPaint);
-        paint->setShader(shader3D)->unref();
+        paint.writable()->setShader(shader3D)->unref();
         shader = shader3D;
     }
 
@@ -865,10 +861,7 @@
         switch (interpret_xfermode(*paint, mode, device.config())) {
             case kSrcOver_XferInterp:
                 mode = NULL;
-                if (!lazyPaint.isValid()) {
-                    paint = lazyPaint.set(origPaint);
-                }
-                paint->setXfermode(NULL);
+                paint.writable()->setXfermode(NULL);
                 break;
             case kSkipDrawing_XferInterp:
                 SK_PLACEMENT_NEW(blitter, SkNullBlitter, storage, storageSize);
@@ -886,18 +879,13 @@
 #endif
             // xfermodes (and filters) require shaders for our current blitters
             shader = SkNEW(SkColorShader);
-            if (!lazyPaint.isValid()) {
-                paint = lazyPaint.set(origPaint);
-            }
-            paint->setShader(shader)->unref();
+            paint.writable()->setShader(shader)->unref();
         } else if (cf) {
             // if no shader && no xfermode, we just apply the colorfilter to
             // our color and move on.
-            if (!lazyPaint.isValid()) {
-                paint = lazyPaint.set(origPaint);
-            }
-            paint->setColor(cf->filterColor(paint->getColor()));
-            paint->setColorFilter(NULL);
+            SkPaint* writablePaint = paint.writable();
+            writablePaint->setColor(cf->filterColor(paint->getColor()));
+            writablePaint->setColorFilter(NULL);
             cf = NULL;
         }
     }
@@ -905,10 +893,7 @@
     if (cf) {
         SkASSERT(shader);
         shader = SkNEW_ARGS(SkFilterShader, (shader, cf));
-        if (!lazyPaint.isValid()) {
-            paint = lazyPaint.set(origPaint);
-        }
-        paint->setShader(shader)->unref();
+        paint.writable()->setShader(shader)->unref();
         // blitters should ignore the presence/absence of a filter, since
         // if there is one, the shader will take care of it.
     }
diff --git a/src/core/SkDraw.cpp b/src/core/SkDraw.cpp
index cdd59c1..885a810 100644
--- a/src/core/SkDraw.cpp
+++ b/src/core/SkDraw.cpp
@@ -931,16 +931,13 @@
     // at this point we're done with prePathMatrix
     SkDEBUGCODE(prePathMatrix = (const SkMatrix*)0x50FF8001;)
 
-    const SkPaint* paint = &origPaint;
-    SkTLazy<SkPaint> lazyPaint;
+    SkTCopyOnFirstWrite<SkPaint> paint(origPaint);
 
     {
         SkScalar coverage;
         if (SkDrawTreatAsHairline(origPaint, *matrix, &coverage)) {
             if (SK_Scalar1 == coverage) {
-                lazyPaint.set(origPaint);
-                lazyPaint.get()->setStrokeWidth(0);
-                paint = lazyPaint.get();
+                paint.writable()->setStrokeWidth(0);
             } else if (xfermodeSupportsCoverageAsAlpha(origPaint.getXfermode())) {
                 U8CPU newAlpha;
 #if 0
@@ -953,10 +950,9 @@
                 int scale = (int)SkScalarMul(coverage, 256);
                 newAlpha = origPaint.getAlpha() * scale >> 8;
 #endif
-                lazyPaint.set(origPaint);
-                lazyPaint.get()->setStrokeWidth(0);
-                lazyPaint.get()->setAlpha(newAlpha);
-                paint = lazyPaint.get();
+                SkPaint* writablePaint = paint.writable();
+                writablePaint->setStrokeWidth(0);
+                writablePaint->setAlpha(newAlpha);
             }
         }
     }
diff --git a/src/device/xps/SkXPSDevice.cpp b/src/device/xps/SkXPSDevice.cpp
index 9c59583..8e62d49 100644
--- a/src/device/xps/SkXPSDevice.cpp
+++ b/src/device/xps/SkXPSDevice.cpp
@@ -1143,15 +1143,14 @@
     SkDEBUGF(("XPS drawVertices not yet implemented."));
 }
 
-void SkXPSDevice::drawPaint(const SkDraw& d, const SkPaint& paint) {
+void SkXPSDevice::drawPaint(const SkDraw& d, const SkPaint& origPaint) {
     const SkRect r = SkRect::MakeSize(this->fCurrentCanvasSize);
 
     //If trying to paint with a stroke, ignore that and fill.
-    SkPaint* fillPaint = const_cast<SkPaint*>(&paint);
-    SkTLazy<SkPaint> modifiedPaint;
-    if (paint.getStyle() != SkPaint::kFill_Style) {
-        fillPaint = modifiedPaint.set(paint);
-        fillPaint->setStyle(SkPaint::kFill_Style);
+    SkPaint* fillPaint = const_cast<SkPaint*>(&origPaint);
+    SkTCopyOnFirstWrite<SkPaint> paint(origPaint);
+    if (paint->getStyle() != SkPaint::kFill_Style) {
+        paint.writable()->setStyle(SkPaint::kFill_Style);
     }
 
     this->internalDrawRect(d, r, false, *fillPaint);
@@ -1640,24 +1639,26 @@
 
 void SkXPSDevice::drawPath(const SkDraw& d,
                            const SkPath& platonicPath,
-                           const SkPaint& paint,
+                           const SkPaint& origPaint,
                            const SkMatrix* prePathMatrix,
                            bool pathIsMutable) {
+    SkTCopyOnFirstWrite<SkPaint> paint(origPaint);
+
     // nothing to draw
     if (d.fClip->isEmpty() ||
-        (paint.getAlpha() == 0 && paint.getXfermode() == NULL)) {
+        (paint->getAlpha() == 0 && paint->getXfermode() == NULL)) {
         return;
     }
 
     SkPath modifiedPath;
-    const bool paintHasPathEffect = paint.getPathEffect()
-                                 || paint.getStyle() != SkPaint::kFill_Style;
+    const bool paintHasPathEffect = paint->getPathEffect()
+                                 || paint->getStyle() != SkPaint::kFill_Style;
 
     //Apply pre-path matrix [Platonic-path -> Skeletal-path].
     SkMatrix matrix = *d.fMatrix;
     SkPath* skeletalPath = const_cast<SkPath*>(&platonicPath);
     if (prePathMatrix) {
-        if (paintHasPathEffect || paint.getRasterizer()) {
+        if (paintHasPathEffect || paint->getRasterizer()) {
             if (!pathIsMutable) {
                 skeletalPath = &modifiedPath;
                 pathIsMutable = true;
@@ -1670,9 +1671,6 @@
         }
     }
 
-    SkTLazy<SkPaint> lazyShaderPaint;
-    SkPaint* shaderPaint = const_cast<SkPaint*>(&paint);
-
     //Apply path effect [Skeletal-path -> Fillable-path].
     SkPath* fillablePath = skeletalPath;
     if (paintHasPathEffect) {
@@ -1680,15 +1678,15 @@
             fillablePath = &modifiedPath;
             pathIsMutable = true;
         }
-        bool fill = paint.getFillPath(*skeletalPath, fillablePath);
+        bool fill = paint->getFillPath(*skeletalPath, fillablePath);
 
-        shaderPaint = lazyShaderPaint.set(*shaderPaint);
-        shaderPaint->setPathEffect(NULL);
+        SkPaint* writablePaint = paint.writable();
+        writablePaint->setPathEffect(NULL);
         if (fill) {
-            shaderPaint->setStyle(SkPaint::kFill_Style);
+            writablePaint->setStyle(SkPaint::kFill_Style);
         } else {
-            shaderPaint->setStyle(SkPaint::kStroke_Style);
-            shaderPaint->setStrokeWidth(0);
+            writablePaint->setStyle(SkPaint::kStroke_Style);
+            writablePaint->setStrokeWidth(0);
         }
     }
 
@@ -1706,22 +1704,13 @@
     HRVM(shadedPath->SetGeometryLocal(shadedGeometry.get()),
          "Could not add the shaded geometry to shaded path.");
 
-    SkRasterizer* rasterizer = paint.getRasterizer();
-    SkMaskFilter* filter = paint.getMaskFilter();
-
-    SkTLazy<SkPaint> lazyRasterizePaint;
-    const SkPaint* rasterizePaint = shaderPaint;
+    SkRasterizer* rasterizer = paint->getRasterizer();
+    SkMaskFilter* filter = paint->getMaskFilter();
 
     //Determine if we will draw or shade and mask.
     if (rasterizer || filter) {
-        if (shaderPaint->getStyle() != SkPaint::kFill_Style) {
-            if (lazyShaderPaint.isValid()) {
-                rasterizePaint = lazyRasterizePaint.set(*shaderPaint);
-            } else {
-                rasterizePaint = shaderPaint;
-                shaderPaint = lazyShaderPaint.set(*shaderPaint);
-            }
-            shaderPaint->setStyle(SkPaint::kFill_Style);
+        if (paint->getStyle() != SkPaint::kFill_Style) {
+            paint.writable()->setStyle(SkPaint::kFill_Style);
         }
     }
 
@@ -1729,7 +1718,7 @@
     BOOL fill;
     BOOL stroke;
     HRV(this->shadePath(shadedPath.get(),
-                        *shaderPaint,
+                        *paint,
                         *d.fMatrix,
                         &fill,
                         &stroke));
@@ -1801,7 +1790,7 @@
                         &matrix,
                         &rasteredMask,
                         SkMask::kComputeBoundsAndRenderImage_CreateMode,
-                        rasterizePaint->getStyle())) {
+                        paint->getStyle())) {
 
             SkAutoMaskFreeImage rasteredAmi(rasteredMask.fImage);
             mask = &rasteredMask;
diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp
index 738521b..8997280 100644
--- a/src/gpu/GrContext.cpp
+++ b/src/gpu/GrContext.cpp
@@ -582,7 +582,7 @@
     this->prepareToDraw(NULL, DEFAULT_BUFFERING)->clear(rect, color, target);
 }
 
-void GrContext::drawPaint(const GrPaint& paint) {
+void GrContext::drawPaint(const GrPaint& origPaint) {
     // set rect to be big enough to fill the space, but not super-huge, so we
     // don't overflow fixed-point implementations
     GrRect r;
@@ -590,8 +590,7 @@
               GrIntToScalar(getRenderTarget()->width()),
               GrIntToScalar(getRenderTarget()->height()));
     GrMatrix inverse;
-    SkTLazy<GrPaint> tmpPaint;
-    const GrPaint* p = &paint;
+    SkTCopyOnFirstWrite<GrPaint> paint(origPaint);
     AutoMatrix am;
 
     // We attempt to map r by the inverse matrix and draw that. mapRect will
@@ -604,25 +603,18 @@
         }
         inverse.mapRect(&r);
     } else {
-        if (paint.hasStage()) {
-            tmpPaint.set(paint);
-            p = tmpPaint.get();
-            if (!tmpPaint.get()->preConcatSamplerMatricesWithInverse(fDrawState->getViewMatrix())) {
+        if (paint->hasStage()) {
+            if (!paint.writable()->preConcatSamplerMatricesWithInverse(fDrawState->getViewMatrix())) {
                 GrPrintf("Could not invert matrix\n");
             }
         }
         am.set(this, GrMatrix::I());
     }
     // by definition this fills the entire clip, no need for AA
-    if (paint.isAntiAlias()) {
-        if (!tmpPaint.isValid()) {
-            tmpPaint.set(paint);
-            p = tmpPaint.get();
-        }
-        GrAssert(p == tmpPaint.get());
-        tmpPaint.get()->setAntiAlias(false);
+    if (paint->isAntiAlias()) {
+        paint.writable()->setAntiAlias(false);
     }
-    this->drawRect(*p, r);
+    this->drawRect(*paint, r);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index b168e57..df7754d 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -20,7 +20,6 @@
 #include "SkDrawProcs.h"
 #include "SkGlyphCache.h"
 #include "SkImageFilter.h"
-#include "SkTLazy.h"
 #include "SkUtils.h"
 
 #define CACHE_COMPATIBLE_DEVICE_TEXTURES 1