Added SkClipStack portion of new clip mask caching system
authorrobertphillips@google.com <robertphillips@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Tue, 7 Aug 2012 15:38:08 +0000 (15:38 +0000)
committerrobertphillips@google.com <robertphillips@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Tue, 7 Aug 2012 15:38:08 +0000 (15:38 +0000)
http://codereview.appspot.com/6449089/

git-svn-id: http://skia.googlecode.com/svn/trunk@4978 2bbb7eff-a529-9590-31e7-b0007b416f81

include/core/SkClipStack.h
src/core/SkClipStack.cpp
src/gpu/SkGpuDevice.cpp

index 4c79c2f..077c4b7 100644 (file)
@@ -10,6 +10,7 @@
 
 #include "SkDeque.h"
 #include "SkRegion.h"
+#include "SkTDArray.h"
 
 struct SkRect;
 class SkPath;
@@ -76,6 +77,29 @@ public:
      */
     bool isWideOpen() const;
 
+    /**
+     * Add a callback function that will be called whenever a clip state
+     * is no longer viable. This will occur whenever restore
+     * is called or when a clipDevRect or clipDevPath call updates the 
+     * clip within an existing save/restore state. Each clip state is
+     * represented by a unique generation ID.
+     */
+    typedef void (*PFPurgeClipCB)(int genID, void* data);
+    void addPurgeClipCallback(PFPurgeClipCB callback, void* data) const;
+
+    /**
+     * Remove a callback added earlier via addPurgeClipCallback
+     */
+    void removePurgeClipCallback(PFPurgeClipCB callback, void* data) const;
+
+    /**
+     * The generation ID has three reserved values to indicate special
+     * (potentially ignoreable) cases
+     */
+    static const int32_t kInvalidGenID = 0;
+    static const int32_t kEmptyGenID = 1;       // no pixels writeable
+    static const int32_t kWideOpenGenID = 2;    // all pixels writeable
+
 private:
     struct Rec;
 
@@ -198,6 +222,33 @@ private:
 
     SkDeque fDeque;
     int     fSaveCount;
+
+    // Generation ID for the clip stack. This is incremented for each
+    // clipDevRect and clipDevPath call. 0 is reserved to indicate an
+    // invalid ID.
+    static int32_t     gGenID;
+
+    struct ClipCallbackData {
+        PFPurgeClipCB   fCallback;
+        void*           fData;
+
+        friend bool operator==(const ClipCallbackData& a,
+                               const ClipCallbackData& b) {
+            return a.fCallback == b.fCallback && a.fData == b.fData;
+        }
+    };
+
+    mutable SkTDArray<ClipCallbackData> fCallbackData;
+
+    /**
+     * Invoke all the purge callbacks passing in rec's generation ID.
+     */
+    void purgeClip(Rec* rec);
+
+    /**
+     * Return the next unique generation ID.
+     */
+    static int32_t GetNextGenID();
 };
 
 #endif
index 38856e5..52b12cf 100644 (file)
@@ -7,9 +7,14 @@
  */
 #include "SkClipStack.h"
 #include "SkPath.h"
+#include "SkThread.h"
+
 #include <new>
 
 
+// 0-2 are reserved for invalid, empty & wide-open
+int32_t SkClipStack::gGenID = 3; 
+
 struct SkClipStack::Rec {
     enum State {
         kEmpty_State,
@@ -41,7 +46,11 @@ struct SkClipStack::Rec {
     SkRect                  fFiniteBound;
     bool                    fIsIntersectionOfRects;
 
-    Rec(int saveCount, const SkRect& rect, SkRegion::Op op, bool doAA) : fRect(rect) {
+    int                     fGenID;
+
+    Rec(int saveCount, const SkRect& rect, SkRegion::Op op, bool doAA) 
+        : fRect(rect)
+        , fGenID(kInvalidGenID) {
         fSaveCount = saveCount;
         fOp = op;
         fState = kRect_State;
@@ -49,7 +58,9 @@ struct SkClipStack::Rec {
         // bounding box members are updated in a following updateBound call
     }
 
-    Rec(int saveCount, const SkPath& path, SkRegion::Op op, bool doAA) : fPath(path) {
+    Rec(int saveCount, const SkPath& path, SkRegion::Op op, bool doAA) 
+        : fPath(path)
+        , fGenID(kInvalidGenID) {
         fRect.setEmpty();
         fSaveCount = saveCount;
         fOp = op;
@@ -63,11 +74,22 @@ struct SkClipStack::Rec {
         fFiniteBound.setEmpty();
         fFiniteBoundType = kNormal_BoundsType;
         fIsIntersectionOfRects = false;
+        fGenID = kEmptyGenID;
+    }
+
+    void checkEmpty() {
+        SkASSERT(fFiniteBound.isEmpty());
+        SkASSERT(kNormal_BoundsType == fFiniteBoundType);
+        SkASSERT(!fIsIntersectionOfRects);
+        SkASSERT(kEmptyGenID == fGenID);
     }
 
     bool operator==(const Rec& b) const {
-        if (fSaveCount != b.fSaveCount || fOp != b.fOp || fState != b.fState ||
-                fDoAA != b.fDoAA) {
+        if (fSaveCount != b.fSaveCount || 
+            fGenID != b.fGenID ||
+            fOp != b.fOp || 
+            fState != b.fState ||
+            fDoAA != b.fDoAA) {
             return false;
         }
         switch (fState) {
@@ -95,6 +117,8 @@ struct SkClipStack::Rec {
                     SkRegion::kIntersect_Op == op)) {
             return true;
         }
+        // Only clips within the same save/restore frame (as captured by
+        // the save count) can be merged
         return  fSaveCount == saveCount && 
                 SkRegion::kIntersect_Op == op &&
                 (SkRegion::kIntersect_Op == fOp || SkRegion::kReplace_Op == fOp);
@@ -408,21 +432,27 @@ struct SkClipStack::Rec {
     }
 };
 
-SkClipStack::SkClipStack() : fDeque(sizeof(Rec)) {
-    fSaveCount = 0;
+
+SkClipStack::SkClipStack() 
+    : fDeque(sizeof(Rec))
+    , fSaveCount(0) {
 }
 
 SkClipStack::SkClipStack(const SkClipStack& b) : fDeque(sizeof(Rec)) {
     *this = b;
 }
 
-SkClipStack::SkClipStack(const SkRect& r) : fDeque(sizeof(Rec)) {
+SkClipStack::SkClipStack(const SkRect& r) 
+    : fDeque(sizeof(Rec))
+    , fSaveCount(0) {
     if (!r.isEmpty()) {
         this->clipDevRect(r, SkRegion::kReplace_Op, false);
     }
 }
 
-SkClipStack::SkClipStack(const SkIRect& r) : fDeque(sizeof(Rec)) {
+SkClipStack::SkClipStack(const SkIRect& r) 
+    : fDeque(sizeof(Rec))
+    , fSaveCount(0) {
     if (!r.isEmpty()) {
         SkRect temp;
         temp.set(r);
@@ -452,7 +482,8 @@ SkClipStack& SkClipStack::operator=(const SkClipStack& b) {
 }
 
 bool SkClipStack::operator==(const SkClipStack& b) const {
-    if (fSaveCount != b.fSaveCount || fDeque.count() != b.fDeque.count()) {
+    if (fSaveCount != b.fSaveCount || 
+        fDeque.count() != b.fDeque.count()) {
         return false;
     }
     SkDeque::F2BIter myIter(fDeque);
@@ -493,6 +524,7 @@ void SkClipStack::restore() {
         if (rec->fSaveCount <= fSaveCount) {
             break;
         }
+        this->purgeClip(rec);
         rec->~Rec();
         fDeque.pop_back();
     }
@@ -524,18 +556,19 @@ void SkClipStack::getBounds(SkRect* canvFiniteBound,
 
 void SkClipStack::clipDevRect(const SkRect& rect, SkRegion::Op op, bool doAA) {
 
+    int32_t genID = GetNextGenID();
+
     SkDeque::Iter iter(fDeque, SkDeque::Iter::kBack_IterStart);
     Rec* rec = (Rec*) iter.prev();
 
     if (rec && rec->canBeIntersectedInPlace(fSaveCount, op)) {
         switch (rec->fState) {
             case Rec::kEmpty_State:
-                SkASSERT(rec->fFiniteBound.isEmpty());
-                SkASSERT(kNormal_BoundsType == rec->fFiniteBoundType);
-                SkASSERT(!rec->fIsIntersectionOfRects);
+                rec->checkEmpty();
                 return;
             case Rec::kRect_State:
                 if (rec->rectRectIntersectAllowed(rect, doAA)) {
+                    this->purgeClip(rec);
                     if (!rec->fRect.intersect(rect)) {
                         rec->setEmpty();
                         return;
@@ -544,11 +577,13 @@ void SkClipStack::clipDevRect(const SkRect& rect, SkRegion::Op op, bool doAA) {
                     rec->fDoAA = doAA;
                     Rec* prev = (Rec*) iter.prev();
                     rec->updateBound(prev);
+                    rec->fGenID = genID;
                     return;
                 }
                 break;
             case Rec::kPath_State:
                 if (!SkRect::Intersects(rec->fPath.getBounds(), rect)) {
+                    this->purgeClip(rec);
                     rec->setEmpty();
                     return;
                 }
@@ -557,6 +592,11 @@ void SkClipStack::clipDevRect(const SkRect& rect, SkRegion::Op op, bool doAA) {
     }
     new (fDeque.push_back()) Rec(fSaveCount, rect, op, doAA);
     ((Rec*) fDeque.back())->updateBound(rec);
+    ((Rec*) fDeque.back())->fGenID = genID;
+
+    if (rec && rec->fSaveCount == fSaveCount) {
+        this->purgeClip(rec);
+    }
 }
 
 void SkClipStack::clipDevPath(const SkPath& path, SkRegion::Op op, bool doAA) {
@@ -564,23 +604,26 @@ void SkClipStack::clipDevPath(const SkPath& path, SkRegion::Op op, bool doAA) {
     if (path.isRect(&alt)) {
         return this->clipDevRect(alt, op, doAA);
     }
+
+    int32_t genID = GetNextGenID();
+
     Rec* rec = (Rec*)fDeque.back();
     if (rec && rec->canBeIntersectedInPlace(fSaveCount, op)) {
         const SkRect& pathBounds = path.getBounds();
         switch (rec->fState) {
             case Rec::kEmpty_State:
-                SkASSERT(rec->fFiniteBound.isEmpty());
-                SkASSERT(kNormal_BoundsType == rec->fFiniteBoundType);
-                SkASSERT(!rec->fIsIntersectionOfRects);
+                rec->checkEmpty();
                 return;
             case Rec::kRect_State:
                 if (!SkRect::Intersects(rec->fRect, pathBounds)) {
+                    this->purgeClip(rec);
                     rec->setEmpty();
                     return;
                 }
                 break;
             case Rec::kPath_State:
                 if (!SkRect::Intersects(rec->fPath.getBounds(), pathBounds)) {
+                    this->purgeClip(rec);
                     rec->setEmpty();
                     return;
                 }
@@ -589,6 +632,11 @@ void SkClipStack::clipDevPath(const SkPath& path, SkRegion::Op op, bool doAA) {
     }
     new (fDeque.push_back()) Rec(fSaveCount, path, op, doAA);
     ((Rec*) fDeque.back())->updateBound(rec);
+    ((Rec*) fDeque.back())->fGenID = genID;
+
+    if (rec && rec->fSaveCount == fSaveCount) {
+        this->purgeClip(rec);
+    }
 }
 
 bool SkClipStack::isWideOpen() const { 
@@ -736,3 +784,32 @@ void SkClipStack::getConservativeBounds(int offsetX,
         devBounds->setEmpty();
     }
 }
+
+void SkClipStack::addPurgeClipCallback(PFPurgeClipCB callback, void* data) const {
+    ClipCallbackData temp = { callback, data };
+    fCallbackData.append(1, &temp);
+}
+
+void SkClipStack::removePurgeClipCallback(PFPurgeClipCB callback, void* data) const {
+    ClipCallbackData temp = { callback, data };
+    int index = fCallbackData.find(temp);
+    if (index >= 0) {
+        fCallbackData.removeShuffle(index);
+    }
+}
+
+// The clip state represented by 'rec' will never be used again. Purge it.
+void SkClipStack::purgeClip(Rec* rec) {
+    SkASSERT(NULL != rec);
+
+    for (int i = 0; i < fCallbackData.count(); ++i) {
+        (*fCallbackData[i].fCallback)(rec->fGenID, fCallbackData[i].fData);
+    }
+
+    // Invalidate rec's gen ID so handlers can detect already handled records
+    rec->fGenID = kInvalidGenID;
+}
+
+int32_t SkClipStack::GetNextGenID() {
+    return sk_atomic_inc(&gGenID);
+}
index 9ed1ccb..592cdfc 100644 (file)
@@ -345,16 +345,35 @@ void SkGpuDevice::writePixels(const SkBitmap& bitmap, int x, int y,
                                config, bitmap.getPixels(), bitmap.rowBytes());
 }
 
+namespace {
+void purgeClipCB(int genID, void* data) {
+    GrContext* context = (GrContext*) data;
+
+    if (SkClipStack::kInvalidGenID == genID ||
+        SkClipStack::kEmptyGenID == genID ||
+        SkClipStack::kWideOpenGenID == genID) {
+        // none of these cases will have a cached clip mask
+        return;
+    }
+
+}
+};
+
 void SkGpuDevice::onAttachToCanvas(SkCanvas* canvas) {
     INHERITED::onAttachToCanvas(canvas);
 
     // Canvas promises that this ptr is valid until onDetachFromCanvas is called
     fClipData.fClipStack = canvas->getClipStack();
+
+    fClipData.fClipStack->addPurgeClipCallback(purgeClipCB, fContext);
 }
 
 void SkGpuDevice::onDetachFromCanvas() {
     INHERITED::onDetachFromCanvas();
 
+    // TODO: iterate through the clip stack and clean up any cached clip masks
+    fClipData.fClipStack->removePurgeClipCallback(purgeClipCB, fContext);
+
     fClipData.fClipStack = NULL;
 }