[WIP] Add Context to SkDrawLooper.

SkDrawLooper carries some state during draws. This CL extracts this state into
a separate class Context, which is then passed by the users of SkDrawLooper
into the appropriate methods.
This is a step towards making SkDrawLooper immutable.

BUG=skia:2141
R=scroggo@google.com, reed@google.com, sugoi@google.com

Author: dominikg@chromium.org

Review URL: https://codereview.chromium.org/155513012

git-svn-id: http://skia.googlecode.com/svn/trunk@13760 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/include/core/SkDrawLooper.h b/include/core/SkDrawLooper.h
index 4609c1d..65650d8 100644
--- a/include/core/SkDrawLooper.h
+++ b/include/core/SkDrawLooper.h
@@ -30,24 +30,49 @@
     SK_DECLARE_INST_COUNT(SkDrawLooper)
 
     /**
-     *  Called right before something is being drawn. This will be followed by
-     *  calls to next() until next() returns false.
+     *  Holds state during a draw. Users call next() until it returns false.
+     *
+     *  Subclasses of SkDrawLooper should create a subclass of this object to
+     *  hold state specific to their subclass.
      */
-    virtual void init(SkCanvas*) = 0;
+    class SK_API Context : public SkNoncopyable {
+    public:
+        Context() {}
+        virtual ~Context() {}
+
+        /**
+         *  Called in a loop on objects returned by SkDrawLooper::createContext().
+         *  Each time true is returned, the object is drawn (possibly with a modified
+         *  canvas and/or paint). When false is finally returned, drawing for the object
+         *  stops.
+         *
+         *  On each call, the paint will be in its original state, but the
+         *  canvas will be as it was following the previous call to next() or
+         *  createContext().
+         *
+         *  The implementation must ensure that, when next() finally returns
+         *  false, the canvas has been restored to the state it was
+         *  initially, before createContext() was first called.
+         */
+        virtual bool next(SkCanvas* canvas, SkPaint* paint) = 0;
+    };
 
     /**
-     *  Called in a loop (after init()). Each time true is returned, the object
-     *  is drawn (possibly with a modified canvas and/or paint). When false is
-     *  finally returned, drawing for the object stops.
-     *
-     *  On each call, the paint will be in its original state, but the canvas
-     *  will be as it was following the previous call to next() or init().
-     *
-     *  The implementation must ensure that, when next() finally returns false,
-     *  that the canvas has been restored to the state it was initially, before
-     *  init() was first called.
+     *  Called right before something is being drawn. Returns a Context
+     *  whose next() method should be called until it returns false.
+     *  The caller has to ensure that the storage pointer provides enough
+     *  memory for the Context. The required size can be queried by calling
+     *  contextSize(). It is also the caller's responsibility to destroy the
+     *  object after use.
      */
-    virtual bool next(SkCanvas*, SkPaint* paint) = 0;
+    virtual Context* createContext(SkCanvas*, void* storage) const = 0;
+
+    /**
+      *  Returns the number of bytes needed to store subclasses of Context (belonging to the
+      *  corresponding SkDrawLooper subclass).
+      */
+    virtual size_t contextSize() const = 0;
+
 
     /**
      * The fast bounds functions are used to enable the paint to be culled early
@@ -59,9 +84,9 @@
      * storage rect, where the storage rect is with the union of the src rect
      * and the looper's bounding rect.
      */
-    virtual bool canComputeFastBounds(const SkPaint& paint);
+    virtual bool canComputeFastBounds(const SkPaint& paint) const;
     virtual void computeFastBounds(const SkPaint& paint,
-                                   const SkRect& src, SkRect* dst);
+                                   const SkRect& src, SkRect* dst) const;
 
     SkDEVCODE(virtual void toString(SkString* str) const = 0;)
     SK_DEFINE_FLATTENABLE_TYPE(SkDrawLooper)
diff --git a/include/effects/SkBlurDrawLooper.h b/include/effects/SkBlurDrawLooper.h
index 46b154d..9a230e5 100644
--- a/include/effects/SkBlurDrawLooper.h
+++ b/include/effects/SkBlurDrawLooper.h
@@ -43,9 +43,9 @@
                      uint32_t flags = kNone_BlurFlag);
     virtual ~SkBlurDrawLooper();
 
-    // overrides from SkDrawLooper
-    virtual void init(SkCanvas*);
-    virtual bool next(SkCanvas*, SkPaint* paint);
+    virtual SkDrawLooper::Context* createContext(SkCanvas*, void* storage) const SK_OVERRIDE;
+
+    virtual size_t contextSize() const SK_OVERRIDE { return sizeof(BlurDrawLooperContext); }
 
     SK_DEVELOPER_TO_STRING()
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkBlurDrawLooper)
@@ -66,7 +66,17 @@
         kAfterEdge,
         kDone
     };
-    State   fState;
+
+    class BlurDrawLooperContext : public SkDrawLooper::Context {
+    public:
+        explicit BlurDrawLooperContext(const SkBlurDrawLooper* looper);
+
+        virtual bool next(SkCanvas* canvas, SkPaint* paint) SK_OVERRIDE;
+
+    private:
+        const SkBlurDrawLooper* fLooper;
+        State fState;
+    };
 
     void init(SkScalar sigma, SkScalar dx, SkScalar dy, SkColor color, uint32_t flags);
 
diff --git a/include/effects/SkLayerDrawLooper.h b/include/effects/SkLayerDrawLooper.h
index 241b3b2..7a13b7a 100644
--- a/include/effects/SkLayerDrawLooper.h
+++ b/include/effects/SkLayerDrawLooper.h
@@ -94,9 +94,9 @@
     /// Similar to addLayer, but adds a layer to the top.
     SkPaint* addLayerOnTop(const LayerInfo&);
 
-    // overrides from SkDrawLooper
-    virtual void init(SkCanvas*);
-    virtual bool next(SkCanvas*, SkPaint* paint);
+    virtual SkDrawLooper::Context* createContext(SkCanvas*, void* storage) const SK_OVERRIDE;
+
+    virtual size_t contextSize() const SK_OVERRIDE { return sizeof(LayerDrawLooperContext); }
 
     SK_DEVELOPER_TO_STRING()
 
@@ -118,9 +118,18 @@
     int     fCount;
 
     // state-machine during the init/next cycle
-    Rec* fCurrRec;
+    class LayerDrawLooperContext : public SkDrawLooper::Context {
+    public:
+        explicit LayerDrawLooperContext(const SkLayerDrawLooper* looper);
 
-    static void ApplyInfo(SkPaint* dst, const SkPaint& src, const LayerInfo&);
+    protected:
+        virtual bool next(SkCanvas*, SkPaint* paint) SK_OVERRIDE;
+
+    private:
+        Rec* fCurrRec;
+
+        static void ApplyInfo(SkPaint* dst, const SkPaint& src, const LayerInfo&);
+    };
 
     class MyRegistrar : public SkFlattenable::Registrar {
     public:
diff --git a/src/core/SkCanvas.cpp b/src/core/SkCanvas.cpp
index bd75f7d..3b7cc5b 100644
--- a/src/core/SkCanvas.cpp
+++ b/src/core/SkCanvas.cpp
@@ -19,6 +19,7 @@
 #include "SkPicture.h"
 #include "SkRasterClip.h"
 #include "SkRRect.h"
+#include "SkSmallAllocator.h"
 #include "SkSurface_Base.h"
 #include "SkTemplates.h"
 #include "SkTextFormatParams.h"
@@ -337,7 +338,6 @@
                    bool skipLayerForImageFilter = false,
                    const SkRect* bounds = NULL) : fOrigPaint(paint) {
         fCanvas = canvas;
-        fLooper = paint.getLooper();
         fFilter = canvas->getDrawFilter();
         fPaint = NULL;
         fSaveCount = canvas->getSaveCount();
@@ -354,10 +354,13 @@
             fDoClearImageFilter = true;
         }
 
-        if (fLooper) {
-            fLooper->init(canvas);
+        if (SkDrawLooper* looper = paint.getLooper()) {
+            void* buffer = fLooperContextAllocator.reserveT<SkDrawLooper::Context>(
+                    looper->contextSize());
+            fLooperContext = looper->createContext(canvas, buffer);
             fIsSimple = false;
         } else {
+            fLooperContext = NULL;
             // can we be marked as simple?
             fIsSimple = !fFilter && !fDoClearImageFilter;
         }
@@ -391,13 +394,14 @@
     SkLazyPaint     fLazyPaint;
     SkCanvas*       fCanvas;
     const SkPaint&  fOrigPaint;
-    SkDrawLooper*   fLooper;
     SkDrawFilter*   fFilter;
     const SkPaint*  fPaint;
     int             fSaveCount;
     bool            fDoClearImageFilter;
     bool            fDone;
     bool            fIsSimple;
+    SkDrawLooper::Context* fLooperContext;
+    SkSmallAllocator<1, 32> fLooperContextAllocator;
 
     bool doNext(SkDrawFilter::Type drawType);
 };
@@ -405,7 +409,7 @@
 bool AutoDrawLooper::doNext(SkDrawFilter::Type drawType) {
     fPaint = NULL;
     SkASSERT(!fIsSimple);
-    SkASSERT(fLooper || fFilter || fDoClearImageFilter);
+    SkASSERT(fLooperContext || fFilter || fDoClearImageFilter);
 
     SkPaint* paint = fLazyPaint.set(fOrigPaint);
 
@@ -413,7 +417,7 @@
         paint->setImageFilter(NULL);
     }
 
-    if (fLooper && !fLooper->next(fCanvas, paint)) {
+    if (fLooperContext && !fLooperContext->next(fCanvas, paint)) {
         fDone = true;
         return false;
     }
@@ -422,7 +426,7 @@
             fDone = true;
             return false;
         }
-        if (NULL == fLooper) {
+        if (NULL == fLooperContext) {
             // no looper means we only draw once
             fDone = true;
         }
@@ -430,7 +434,7 @@
     fPaint = paint;
 
     // if we only came in here for the imagefilter, mark us as done
-    if (!fLooper && !fFilter) {
+    if (!fLooperContext && !fFilter) {
         fDone = true;
     }
 
diff --git a/src/core/SkDrawLooper.cpp b/src/core/SkDrawLooper.cpp
index bac2d96..c620cd0 100644
--- a/src/core/SkDrawLooper.cpp
+++ b/src/core/SkDrawLooper.cpp
@@ -10,14 +10,17 @@
 #include "SkMatrix.h"
 #include "SkPaint.h"
 #include "SkRect.h"
+#include "SkSmallAllocator.h"
 
-bool SkDrawLooper::canComputeFastBounds(const SkPaint& paint) {
+bool SkDrawLooper::canComputeFastBounds(const SkPaint& paint) const {
     SkCanvas canvas;
+    SkSmallAllocator<1, 32> allocator;
+    void* buffer = allocator.reserveT<SkDrawLooper::Context>(this->contextSize());
 
-    this->init(&canvas);
+    SkDrawLooper::Context* context = this->createContext(&canvas, buffer);
     for (;;) {
         SkPaint p(paint);
-        if (this->next(&canvas, &p)) {
+        if (context->next(&canvas, &p)) {
             p.setLooper(NULL);
             if (!p.canComputeFastBounds()) {
                 return false;
@@ -30,14 +33,16 @@
 }
 
 void SkDrawLooper::computeFastBounds(const SkPaint& paint, const SkRect& src,
-                                     SkRect* dst) {
+                                     SkRect* dst) const {
     SkCanvas canvas;
+    SkSmallAllocator<1, 32> allocator;
+    void* buffer = allocator.reserveT<SkDrawLooper::Context>(this->contextSize());
 
     *dst = src;   // catch case where there are no loops
-    this->init(&canvas);
+    SkDrawLooper::Context* context = this->createContext(&canvas, buffer);
     for (bool firstTime = true;; firstTime = false) {
         SkPaint p(paint);
-        if (this->next(&canvas, &p)) {
+        if (context->next(&canvas, &p)) {
             SkRect r(src);
 
             p.setLooper(NULL);
diff --git a/src/core/SkSmallAllocator.h b/src/core/SkSmallAllocator.h
index d5ebf3b..2eddb51 100644
--- a/src/core/SkSmallAllocator.h
+++ b/src/core/SkSmallAllocator.h
@@ -96,20 +96,21 @@
         return static_cast<T*>(buf);
     }
 
-private:
     /*
-     *  Helper function to provide space for one T. The space will be in
-     *  fStorage if there is room, or on the heap otherwise. Either way, this
-     *  class will call ~T() in its destructor and free the heap allocation if
-     *  necessary.
+     *  Reserve a specified amount of space (must be enough space for one T).
+     *  The space will be in fStorage if there is room, or on the heap otherwise.
+     *  Either way, this class will call ~T() in its destructor and free the heap
+     *  allocation if necessary.
+     *  Unlike createT(), this method will not call the constructor of T.
      */
-    template<typename T> void* reserveT() {
+    template<typename T> void* reserveT(size_t storageRequired = sizeof(T)) {
         SkASSERT(fNumObjects < kMaxObjects);
+        SkASSERT(storageRequired >= sizeof(T));
         if (kMaxObjects == fNumObjects) {
             return NULL;
         }
         const size_t storageRemaining = SkAlign4(kTotalBytes) - fStorageUsed;
-        const size_t storageRequired = SkAlign4(sizeof(T));
+        storageRequired = SkAlign4(storageRequired);
         Rec* rec = &fRecs[fNumObjects];
         if (storageRequired > storageRemaining) {
             // Allocate on the heap. Ideally we want to avoid this situation,
diff --git a/src/effects/SkBlurDrawLooper.cpp b/src/effects/SkBlurDrawLooper.cpp
index 3a5c697..0bbc184 100644
--- a/src/effects/SkBlurDrawLooper.cpp
+++ b/src/effects/SkBlurDrawLooper.cpp
@@ -33,7 +33,6 @@
     fDy = dy;
     fBlurColor = color;
     fBlurFlags = flags;
-    fState = kDone;
 
     SkASSERT(flags <= kAll_BlurFlag);
     if (sigma > 0) {
@@ -90,11 +89,16 @@
     buffer.writeUInt(fBlurFlags);
 }
 
-void SkBlurDrawLooper::init(SkCanvas*) {
-    fState = kBeforeEdge;
+SkDrawLooper::Context* SkBlurDrawLooper::createContext(SkCanvas*, void* storage) const {
+    return SkNEW_PLACEMENT_ARGS(storage, BlurDrawLooperContext, (this));
 }
 
-bool SkBlurDrawLooper::next(SkCanvas* canvas, SkPaint* paint) {
+SkBlurDrawLooper::BlurDrawLooperContext::BlurDrawLooperContext(
+        const SkBlurDrawLooper* looper)
+    : fLooper(looper), fState(SkBlurDrawLooper::kBeforeEdge) {}
+
+bool SkBlurDrawLooper::BlurDrawLooperContext::next(SkCanvas* canvas,
+                                                   SkPaint* paint) {
     switch (fState) {
         case kBeforeEdge:
             // we do nothing if a maskfilter is already installed
@@ -104,23 +108,23 @@
             }
 #ifdef SK_BUILD_FOR_ANDROID
             SkColor blurColor;
-            blurColor = fBlurColor;
+            blurColor = fLooper->fBlurColor;
             if (SkColorGetA(blurColor) == 255) {
                 blurColor = SkColorSetA(blurColor, paint->getAlpha());
             }
             paint->setColor(blurColor);
 #else
-            paint->setColor(fBlurColor);
+            paint->setColor(fLooper->fBlurColor);
 #endif
-            paint->setMaskFilter(fBlur);
-            paint->setColorFilter(fColorFilter);
+            paint->setMaskFilter(fLooper->fBlur);
+            paint->setColorFilter(fLooper->fColorFilter);
             canvas->save(SkCanvas::kMatrix_SaveFlag);
-            if (fBlurFlags & kIgnoreTransform_BlurFlag) {
+            if (fLooper->fBlurFlags & kIgnoreTransform_BlurFlag) {
                 SkMatrix transform(canvas->getTotalMatrix());
-                transform.postTranslate(fDx, fDy);
+                transform.postTranslate(fLooper->fDx, fLooper->fDy);
                 canvas->setMatrix(transform);
             } else {
-                canvas->translate(fDx, fDy);
+                canvas->translate(fLooper->fDx, fLooper->fDy);
             }
             fState = kAfterEdge;
             return true;
diff --git a/src/effects/SkLayerDrawLooper.cpp b/src/effects/SkLayerDrawLooper.cpp
index cfe673d..6d31c23 100644
--- a/src/effects/SkLayerDrawLooper.cpp
+++ b/src/effects/SkLayerDrawLooper.cpp
@@ -24,8 +24,7 @@
 SkLayerDrawLooper::SkLayerDrawLooper()
         : fRecs(NULL),
           fTopRec(NULL),
-          fCount(0),
-          fCurrRec(NULL) {
+          fCount(0) {
 }
 
 SkLayerDrawLooper::~SkLayerDrawLooper() {
@@ -75,9 +74,9 @@
     return &rec->fPaint;
 }
 
-void SkLayerDrawLooper::init(SkCanvas* canvas) {
-    fCurrRec = fRecs;
+SkLayerDrawLooper::Context* SkLayerDrawLooper::createContext(SkCanvas* canvas, void* storage) const {
     canvas->save(SkCanvas::kMatrix_SaveFlag);
+    return SkNEW_PLACEMENT_ARGS(storage, LayerDrawLooperContext, (this));
 }
 
 static SkColor xferColor(SkColor src, SkColor dst, SkXfermode::Mode mode) {
@@ -98,8 +97,8 @@
 // Even with kEntirePaint_Bits, we always ensure that the master paint's
 // text-encoding is respected, since that controls how we interpret the
 // text/length parameters of a draw[Pos]Text call.
-void SkLayerDrawLooper::ApplyInfo(SkPaint* dst, const SkPaint& src,
-                                  const LayerInfo& info) {
+void SkLayerDrawLooper::LayerDrawLooperContext::ApplyInfo(
+        SkPaint* dst, const SkPaint& src, const LayerInfo& info) {
 
     dst->setColor(xferColor(src.getColor(), dst->getColor(), info.fColorMode));
 
@@ -167,7 +166,11 @@
     canvas->setMatrix(m);
 }
 
-bool SkLayerDrawLooper::next(SkCanvas* canvas, SkPaint* paint) {
+SkLayerDrawLooper::LayerDrawLooperContext::LayerDrawLooperContext(
+        const SkLayerDrawLooper* looper) : fCurrRec(looper->fRecs) {}
+
+bool SkLayerDrawLooper::LayerDrawLooperContext::next(SkCanvas* canvas,
+                                                     SkPaint* paint) {
     canvas->restore();
     if (NULL == fCurrRec) {
         return false;
@@ -180,7 +183,8 @@
         postTranslate(canvas, fCurrRec->fInfo.fOffset.fX,
                       fCurrRec->fInfo.fOffset.fY);
     } else {
-        canvas->translate(fCurrRec->fInfo.fOffset.fX, fCurrRec->fInfo.fOffset.fY);
+        canvas->translate(fCurrRec->fInfo.fOffset.fX,
+                          fCurrRec->fInfo.fOffset.fY);
     }
     fCurrRec = fCurrRec->fNext;
 
diff --git a/tests/LayerDrawLooperTest.cpp b/tests/LayerDrawLooperTest.cpp
index 68dd5e0..1facb23 100644
--- a/tests/LayerDrawLooperTest.cpp
+++ b/tests/LayerDrawLooperTest.cpp
@@ -15,6 +15,7 @@
 #include "SkRect.h"
 #include "SkRefCnt.h"
 #include "SkScalar.h"
+#include "SkSmallAllocator.h"
 #include "SkXfermode.h"
 #include "Test.h"
 
@@ -57,10 +58,12 @@
     SkCanvas canvas(&device);
     SkPaint paint;
     SkAutoTUnref<SkLayerDrawLooper> looper(looperBuilder.detachLooper());
-    looper->init(&canvas);
+    SkSmallAllocator<1, 32> allocator;
+    void* buffer = allocator.reserveT<SkDrawLooper::Context>(looper->contextSize());
+    SkDrawLooper::Context* context = looper->createContext(&canvas, buffer);
 
     // The back layer should come first.
-    REPORTER_ASSERT(reporter, looper->next(&canvas, &paint));
+    REPORTER_ASSERT(reporter, context->next(&canvas, &paint));
     REPORTER_ASSERT(reporter, SkXfermode::IsMode(paint.getXfermode(), SkXfermode::kSrc_Mode));
     canvas.drawRect(SkRect::MakeWH(50.0f, 50.0f), paint);
     REPORTER_ASSERT(reporter, 10.0f == device.fLastMatrix.getTranslateX());
@@ -68,14 +71,14 @@
     paint.reset();
 
     // Then the front layer.
-    REPORTER_ASSERT(reporter, looper->next(&canvas, &paint));
+    REPORTER_ASSERT(reporter, context->next(&canvas, &paint));
     REPORTER_ASSERT(reporter, SkXfermode::IsMode(paint.getXfermode(), SkXfermode::kSrcOver_Mode));
     canvas.drawRect(SkRect::MakeWH(50.0f, 50.0f), paint);
     REPORTER_ASSERT(reporter, 0.0f == device.fLastMatrix.getTranslateX());
     REPORTER_ASSERT(reporter, 0.0f == device.fLastMatrix.getTranslateY());
 
     // Only two layers were added, so that should be the end.
-    REPORTER_ASSERT(reporter, !looper->next(&canvas, &paint));
+    REPORTER_ASSERT(reporter, !context->next(&canvas, &paint));
 }
 
 static void test_backToFront(skiatest::Reporter* reporter) {
@@ -95,10 +98,12 @@
     SkCanvas canvas(&device);
     SkPaint paint;
     SkAutoTUnref<SkLayerDrawLooper> looper(looperBuilder.detachLooper());
-    looper->init(&canvas);
+    SkSmallAllocator<1, 32> allocator;
+    void* buffer = allocator.reserveT<SkDrawLooper::Context>(looper->contextSize());
+    SkDrawLooper::Context* context = looper->createContext(&canvas, buffer);
 
     // The back layer should come first.
-    REPORTER_ASSERT(reporter, looper->next(&canvas, &paint));
+    REPORTER_ASSERT(reporter, context->next(&canvas, &paint));
     REPORTER_ASSERT(reporter, SkXfermode::IsMode(paint.getXfermode(), SkXfermode::kSrcOver_Mode));
     canvas.drawRect(SkRect::MakeWH(50.0f, 50.0f), paint);
     REPORTER_ASSERT(reporter, 0.0f == device.fLastMatrix.getTranslateX());
@@ -106,14 +111,14 @@
     paint.reset();
 
     // Then the front layer.
-    REPORTER_ASSERT(reporter, looper->next(&canvas, &paint));
+    REPORTER_ASSERT(reporter, context->next(&canvas, &paint));
     REPORTER_ASSERT(reporter, SkXfermode::IsMode(paint.getXfermode(), SkXfermode::kSrc_Mode));
     canvas.drawRect(SkRect::MakeWH(50.0f, 50.0f), paint);
     REPORTER_ASSERT(reporter, 10.0f == device.fLastMatrix.getTranslateX());
     REPORTER_ASSERT(reporter, 20.0f == device.fLastMatrix.getTranslateY());
 
     // Only two layers were added, so that should be the end.
-    REPORTER_ASSERT(reporter, !looper->next(&canvas, &paint));
+    REPORTER_ASSERT(reporter, !context->next(&canvas, &paint));
 }
 
 static void test_mixed(skiatest::Reporter* reporter) {
@@ -133,10 +138,12 @@
     SkCanvas canvas(&device);
     SkPaint paint;
     SkAutoTUnref<SkLayerDrawLooper> looper(looperBuilder.detachLooper());
-    looper->init(&canvas);
+    SkSmallAllocator<1, 32> allocator;
+    void* buffer = allocator.reserveT<SkDrawLooper::Context>(looper->contextSize());
+    SkDrawLooper::Context* context = looper->createContext(&canvas, buffer);
 
     // The back layer should come first.
-    REPORTER_ASSERT(reporter, looper->next(&canvas, &paint));
+    REPORTER_ASSERT(reporter, context->next(&canvas, &paint));
     REPORTER_ASSERT(reporter, SkXfermode::IsMode(paint.getXfermode(), SkXfermode::kSrcOver_Mode));
     canvas.drawRect(SkRect::MakeWH(50.0f, 50.0f), paint);
     REPORTER_ASSERT(reporter, 0.0f == device.fLastMatrix.getTranslateX());
@@ -144,14 +151,14 @@
     paint.reset();
 
     // Then the front layer.
-    REPORTER_ASSERT(reporter, looper->next(&canvas, &paint));
+    REPORTER_ASSERT(reporter, context->next(&canvas, &paint));
     REPORTER_ASSERT(reporter, SkXfermode::IsMode(paint.getXfermode(), SkXfermode::kSrc_Mode));
     canvas.drawRect(SkRect::MakeWH(50.0f, 50.0f), paint);
     REPORTER_ASSERT(reporter, 10.0f == device.fLastMatrix.getTranslateX());
     REPORTER_ASSERT(reporter, 20.0f == device.fLastMatrix.getTranslateY());
 
     // Only two layers were added, so that should be the end.
-    REPORTER_ASSERT(reporter, !looper->next(&canvas, &paint));
+    REPORTER_ASSERT(reporter, !context->next(&canvas, &paint));
 }
 
 DEF_TEST(LayerDrawLooper, reporter) {
diff --git a/tests/QuickRejectTest.cpp b/tests/QuickRejectTest.cpp
index 2d36738..ef14552 100644
--- a/tests/QuickRejectTest.cpp
+++ b/tests/QuickRejectTest.cpp
@@ -7,6 +7,7 @@
 
 #include "SkCanvas.h"
 #include "SkDrawLooper.h"
+#include "SkTypes.h"
 #include "Test.h"
 
 /*
@@ -14,20 +15,12 @@
  */
 class TestLooper : public SkDrawLooper {
 public:
-    bool fOnce;
 
-    virtual void init(SkCanvas*) SK_OVERRIDE {
-        fOnce = true;
+    virtual SkDrawLooper::Context* createContext(SkCanvas*, void* storage) const SK_OVERRIDE {
+        return SkNEW_PLACEMENT(storage, TestDrawLooperContext);
     }
 
-    virtual bool next(SkCanvas* canvas, SkPaint*) SK_OVERRIDE {
-        if (fOnce) {
-            fOnce = false;
-            canvas->translate(SkIntToScalar(10), 0);
-            return true;
-        }
-        return false;
-    }
+    virtual size_t contextSize() const SK_OVERRIDE { return sizeof(TestDrawLooperContext); }
 
 #ifdef SK_DEVELOPER
     virtual void toString(SkString* str) const SK_OVERRIDE {
@@ -35,6 +28,24 @@
     }
 #endif
 
+private:
+    class TestDrawLooperContext : public SkDrawLooper::Context {
+    public:
+        TestDrawLooperContext() : fOnce(true) {}
+        virtual ~TestDrawLooperContext() {}
+
+        virtual bool next(SkCanvas* canvas, SkPaint*) SK_OVERRIDE {
+            if (fOnce) {
+                fOnce = false;
+                canvas->translate(SkIntToScalar(10), 0);
+                return true;
+            }
+            return false;
+        }
+    private:
+        bool fOnce;
+    };
+
     SK_DECLARE_UNFLATTENABLE_OBJECT()
 };