diff --git a/include/core/SkClipStack.h b/include/core/SkClipStack.h index 4c79c2f824..077c4b7eea 100644 --- a/include/core/SkClipStack.h +++ b/include/core/SkClipStack.h @@ -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 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 diff --git a/src/core/SkClipStack.cpp b/src/core/SkClipStack.cpp index 38856e586a..52b12cf779 100644 --- a/src/core/SkClipStack.cpp +++ b/src/core/SkClipStack.cpp @@ -7,9 +7,14 @@ */ #include "SkClipStack.h" #include "SkPath.h" +#include "SkThread.h" + #include +// 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); +} diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp index 9ed1ccbdf0..592cdfc6e6 100644 --- a/src/gpu/SkGpuDevice.cpp +++ b/src/gpu/SkGpuDevice.cpp @@ -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; }