#include "SkDeque.h"
#include "SkRegion.h"
+#include "SkTDArray.h"
struct SkRect;
class SkPath;
*/
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;
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
*/
#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,
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;
// 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;
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) {
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);
}
};
-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);
}
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);
if (rec->fSaveCount <= fSaveCount) {
break;
}
+ this->purgeClip(rec);
rec->~Rec();
fDeque.pop_back();
}
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;
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;
}
}
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) {
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;
}
}
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 {
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);
+}