Added stack to AA clip mask cache

http://codereview.appspot.com/6201058/



git-svn-id: http://skia.googlecode.com/svn/trunk@3885 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/gyp/tests.gyp b/gyp/tests.gyp
index 58039ba..e7d5724 100644
--- a/gyp/tests.gyp
+++ b/gyp/tests.gyp
@@ -23,6 +23,7 @@
         '../tests/BlurTest.cpp',
         '../tests/CanvasTest.cpp',
         '../tests/ClampRangeTest.cpp',
+        '../tests/ClipCacheTest.cpp',
         '../tests/ClipCubicTest.cpp',
         '../tests/ClipStackTest.cpp',
         '../tests/ClipperTest.cpp',
diff --git a/src/gpu/GrClipMaskManager.cpp b/src/gpu/GrClipMaskManager.cpp
index 1a892d8..f8c43b0 100644
--- a/src/gpu/GrClipMaskManager.cpp
+++ b/src/gpu/GrClipMaskManager.cpp
@@ -396,7 +396,7 @@
 
     if (fAACache.canReuse(clipIn, rt->width(), rt->height())) {
         *result = fAACache.getLastMask();
-        *resultBounds = fAACache.getLastBound();
+        fAACache.getLastBound(resultBounds);
         return true;
     }
 
diff --git a/src/gpu/GrClipMaskManager.h b/src/gpu/GrClipMaskManager.h
index f827948..6520567 100644
--- a/src/gpu/GrClipMaskManager.h
+++ b/src/gpu/GrClipMaskManager.h
@@ -15,6 +15,7 @@
 #include "GrClip.h"
 #include "SkRefCnt.h"
 #include "GrTexture.h"
+#include "SkDeque.h"
 
 class GrGpu;
 class GrPathRenderer;
@@ -42,22 +43,34 @@
  */
 class GrClipMaskCache : public GrNoncopyable {
 public:
-    GrClipMaskCache() {
-        reset();
+    GrClipMaskCache() 
+    : fStack(sizeof(GrClipStackFrame)) {
+        // We need an initial frame to capture the clip state prior to 
+        // any pushes
+        new (fStack.push_back()) GrClipStackFrame();
     }
 
-    void reset () {
-        fLastWidth = -1;
-        fLastHeight = -1;
-        fLastClip.setEmpty();
-        fLastMask.reset(NULL);
-        fLastBound.MakeEmpty();
+    ~GrClipMaskCache() {
+
+        while (!fStack.empty()) {
+            GrClipStackFrame* temp = (GrClipStackFrame*) fStack.back();
+            temp->~GrClipStackFrame();
+            fStack.pop_back();
+        }
     }
 
     bool canReuse(const GrClip& clip, int width, int height) {
-        if (fLastWidth >= width &&
-            fLastHeight >= height &&
-            clip == fLastClip) {
+
+        if (fStack.empty()) {
+            GrAssert(false);
+            return false;
+        }
+
+        GrClipStackFrame* back = (GrClipStackFrame*) fStack.back();
+
+        if (back->fLastWidth >= width &&
+            back->fLastHeight >= height &&
+            clip == back->fLastClip) {
             return true;
         }
 
@@ -67,61 +80,204 @@
     void set(const GrClip& clip, int width, int height, 
              GrTexture* mask, const GrRect& bound) {
 
-        fLastWidth = width;
-        fLastHeight = height;
-        fLastClip = clip;
+        if (fStack.empty()) {
+            GrAssert(false);
+            return;
+        }
+
+        GrClipStackFrame* back = (GrClipStackFrame*) fStack.back();
+
+        back->fLastWidth = width;
+        back->fLastHeight = height;
+        back->fLastClip = clip;
         SkSafeRef(mask);
-        fLastMask.reset(mask);
-        fLastBound = bound;
+        back->fLastMask.reset(mask);
+        back->fLastBound = bound;
+    }
+
+    void reset() {
+        if (fStack.empty()) {
+            GrAssert(false);
+            return;
+        }
+
+        GrClipStackFrame* back = (GrClipStackFrame*) fStack.back();
+
+        back->reset();
+    }
+
+    /**
+     * After a "push" the clip state is entirely open. Currently, the
+     * entire clip stack will be re-rendered into a new clip mask.
+     * TODO: can we take advantage of the nested nature of the clips to
+     * reduce the mask creation cost?
+     */
+    void push() {
+        new (fStack.push_back()) GrClipStackFrame();
+    }
+
+    void pop() {
+        GrAssert(!fStack.empty());
+
+        if (!fStack.empty()) {
+            GrClipStackFrame* back = (GrClipStackFrame*) fStack.back();
+
+            back->~GrClipStackFrame();
+            fStack.pop_back();
+        }
     }
 
     int getLastWidth() const {
-        return fLastWidth;
+
+        if (fStack.empty()) {
+            GrAssert(false);
+            return -1;
+        }
+
+        GrClipStackFrame* back = (GrClipStackFrame*) fStack.back();
+
+        return back->fLastWidth;
     }
 
     int getLastHeight() const {
-        return fLastHeight;
+
+        if (fStack.empty()) {
+            GrAssert(false);
+            return -1;
+        }
+
+        GrClipStackFrame* back = (GrClipStackFrame*) fStack.back();
+
+        return back->fLastHeight;
     }
 
-    const GrClip& getLastClip() const {
-        return fLastClip;
+    void getLastClip(GrClip* clip) const {
+
+        if (fStack.empty()) {
+            GrAssert(false);
+            clip->setEmpty();
+            return;
+        }
+
+        GrClipStackFrame* back = (GrClipStackFrame*) fStack.back();
+
+        *clip = back->fLastClip;
     }
 
     GrTexture* getLastMask() {
-        return fLastMask.get();
+
+        if (fStack.empty()) {
+            GrAssert(false);
+            return NULL;
+        }
+
+        GrClipStackFrame* back = (GrClipStackFrame*) fStack.back();
+
+        return back->fLastMask.get();
+    }
+
+    const GrTexture* getLastMask() const {
+
+        if (fStack.empty()) {
+            GrAssert(false);
+            return NULL;
+        }
+
+        GrClipStackFrame* back = (GrClipStackFrame*) fStack.back();
+
+        return back->fLastMask.get();
     }
 
     GrTexture* detachLastMask() {
-        return fLastMask.detach();
+
+        if (fStack.empty()) {
+            GrAssert(false);
+            return NULL;
+        }
+
+        GrClipStackFrame* back = (GrClipStackFrame*) fStack.back();
+
+        return back->fLastMask.detach();
     }
 
     int getLastMaskWidth() const {
-        if (NULL == fLastMask.get()) {
+
+        if (fStack.empty()) {
+            GrAssert(false);
             return -1;
         }
 
-        return fLastMask.get()->width();
+        GrClipStackFrame* back = (GrClipStackFrame*) fStack.back();
+
+        if (NULL == back->fLastMask.get()) {
+            return -1;
+        }
+
+        return back->fLastMask.get()->width();
     }
 
     int getLastMaskHeight() const {
-        if (NULL == fLastMask.get()) {
+
+        if (fStack.empty()) {
+            GrAssert(false);
             return -1;
         }
 
-        return fLastMask.get()->height();
+        GrClipStackFrame* back = (GrClipStackFrame*) fStack.back();
+
+        if (NULL == back->fLastMask.get()) {
+            return -1;
+        }
+
+        return back->fLastMask.get()->height();
     }
 
-    const GrRect& getLastBound() const {
-        return fLastBound;
+    void getLastBound(GrRect* bound) const {
+
+        if (fStack.empty()) {
+            GrAssert(false);
+            bound->setEmpty();
+            return;
+        }
+
+        GrClipStackFrame* back = (GrClipStackFrame*) fStack.back();
+
+        *bound = back->fLastBound;
     }
 
 protected:
 private:
-    int                     fLastWidth;
-    int                     fLastHeight;
-    GrClip                  fLastClip;
-    SkAutoTUnref<GrTexture> fLastMask;
-    GrRect                  fLastBound;
+    struct GrClipStackFrame {
+
+        GrClipStackFrame() {
+            reset();
+        }
+
+        void reset () {
+            fLastWidth = -1;
+            fLastHeight = -1;
+            fLastClip.setEmpty();
+            fLastMask.reset(NULL);
+            fLastBound.setEmpty();
+        }
+
+        // fLastWidth & fLastHeight store the render target size used when
+        // creating the mask. They factor into the reuse decision (in canReuse)
+        // TODO: We should probably use the mask's width & height rather than
+        // the render target's width & height for reuse decisions
+        int                     fLastWidth;
+        int                     fLastHeight;
+        GrClip                  fLastClip;
+        // The mask's width & height values are used in setupDrawStateAAClip to 
+        // correctly scale the uvs for geometry drawn with this mask
+        SkAutoTUnref<GrTexture> fLastMask;
+        // fLastBound stores the bounding box of the clip mask in canvas 
+        // space. The left and top fields are used to offset the uvs for 
+        // geometry drawn with this mask (in setupDrawStateAAClip)
+        GrRect                  fLastBound;
+    };
+
+    SkDeque      fStack;
 
     typedef GrNoncopyable INHERITED;
 };
diff --git a/src/gpu/GrSoftwarePathRenderer.cpp b/src/gpu/GrSoftwarePathRenderer.cpp
index cbec965..22e4a26 100644
--- a/src/gpu/GrSoftwarePathRenderer.cpp
+++ b/src/gpu/GrSoftwarePathRenderer.cpp
@@ -232,6 +232,7 @@
             // rendering (kGlyphMaskStage in GrBatchedTextContext)
             kPathMaskStage = GrPaint::kTotalStages,
         };
+        GrAssert(NULL == target->drawState()->getTexture(kPathMaskStage));
         target->drawState()->setTexture(kPathMaskStage, texture);
         target->drawState()->sampler(kPathMaskStage)->reset();
         GrScalar w = GrIntToScalar(pathBounds.width());
diff --git a/tests/ClipCacheTest.cpp b/tests/ClipCacheTest.cpp
new file mode 100644
index 0000000..dbcb4e2
--- /dev/null
+++ b/tests/ClipCacheTest.cpp
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "Test.h"
+#include "SkGpuDevice.h"
+#include "../../src/gpu/GrClipMaskManager.h"
+
+static const int X_SIZE = 12;
+static const int Y_SIZE = 12;
+
+////////////////////////////////////////////////////////////////////////////////
+static GrTexture* createTexture(GrContext* context) {
+    unsigned char textureData[X_SIZE][Y_SIZE];
+
+    memset(textureData, 0, X_SIZE * Y_SIZE);
+
+    GrTextureDesc desc;
+
+    // let Skia know we will be using this texture as a render target
+    desc.fFlags     = kRenderTarget_GrTextureFlagBit;
+    // it is a single channel texture
+    desc.fConfig    = kAlpha_8_GrPixelConfig;
+    desc.fWidth     = X_SIZE;
+    desc.fHeight    = Y_SIZE;
+    desc.fSampleCnt = 0;
+
+    // We are initializing the texture with zeros here
+    GrTexture* texture = context->createUncachedTexture(desc, textureData, 0);
+    if (!texture) {
+        return NULL;
+    }
+
+    return texture;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// verify that the top state of the stack matches the passed in state
+static void check_state(skiatest::Reporter* reporter,
+                        const GrClipMaskCache& cache,
+                        int width,
+                        int height,
+                        const GrClip& clip,
+                        GrTexture* mask,
+                        const GrRect& bound) {
+    REPORTER_ASSERT(reporter, width == cache.getLastWidth());
+    REPORTER_ASSERT(reporter, height == cache.getLastHeight());
+
+    GrClip cacheClip;
+    cache.getLastClip(&cacheClip);
+    REPORTER_ASSERT(reporter, clip == cacheClip);
+
+    REPORTER_ASSERT(reporter, mask == cache.getLastMask());
+
+    GrRect cacheBound;
+    cache.getLastBound(&cacheBound);
+    REPORTER_ASSERT(reporter, bound == cacheBound);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// basic test of the cache's base functionality:
+//  push, pop, set, canReuse & getters
+static void test_cache(skiatest::Reporter* reporter, GrContext* context) {
+
+    GrClipMaskCache cache;
+
+    GrClip emptyClip;
+    emptyClip.setEmpty();
+
+    GrRect emptyBound;
+    emptyBound.setEmpty();
+
+    // check initial state
+    check_state(reporter, cache, -1, -1, emptyClip, NULL, emptyBound);
+
+    // set the current state
+    GrRect bound1;
+    bound1.set(0, 0, 100, 100);
+
+    GrClip clip1;
+    clip1.setFromRect(bound1);
+
+    SkAutoTUnref<GrTexture> texture(createTexture(context));
+
+    cache.set(clip1, 128, 128, texture.get(), bound1);
+
+    // check that the set took
+    check_state(reporter, cache, 128, 128, clip1, texture.get(), bound1);
+    REPORTER_ASSERT(reporter, 2 == texture.get()->getRefCnt());
+
+    // push the state
+    cache.push();
+
+    // verify that the pushed state is initially empty
+    check_state(reporter, cache, -1, -1, emptyClip, NULL, emptyBound);
+    REPORTER_ASSERT(reporter, 2 == texture.get()->getRefCnt());
+
+    // modify the new state
+    GrRect bound2;
+    bound2.set(-10, -10, 10, 10);
+
+    GrClip clip2;
+    clip2.setEmpty();
+    clip2.setFromRect(bound2);
+
+    cache.set(clip2, 10, 10, texture.get(), bound2);
+
+    // check that the changes took
+    check_state(reporter, cache, 10, 10, clip2, texture.get(), bound2);
+    REPORTER_ASSERT(reporter, 3 == texture.get()->getRefCnt());
+
+    // check to make sure canReuse works
+    REPORTER_ASSERT(reporter, cache.canReuse(clip2, 10, 10));
+    REPORTER_ASSERT(reporter, !cache.canReuse(clip1, 10, 10));
+
+    // pop the state
+    cache.pop();
+
+    // verify that the old state is restored
+    check_state(reporter, cache, 128, 128, clip1, texture.get(), bound1);
+    REPORTER_ASSERT(reporter, 2 == texture.get()->getRefCnt());
+
+    // manually clear the state
+    cache.reset();
+
+    // verify it is now empty
+    check_state(reporter, cache, -1, -1, emptyClip, NULL, emptyBound);
+    REPORTER_ASSERT(reporter, 1 == texture.get()->getRefCnt());
+
+    // pop again - so there is no state
+    cache.pop();
+
+#if !defined(SK_DEBUG)
+    // verify that the getters don't crash
+    // only do in release since it generates asserts in debug
+    check_state(reporter, cache, -1, -1, emptyClip, NULL, emptyBound);
+#endif
+    REPORTER_ASSERT(reporter, 1 == texture.get()->getRefCnt());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+static void TestClipCache(skiatest::Reporter* reporter, GrContext* context) {
+
+    test_cache(reporter, context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+#include "TestClassDef.h"
+DEFINE_GPUTESTCLASS("ClipCache", ClipCacheTestClass, TestClipCache)