Index: src/core/SkPictureFlat.h |
diff --git a/src/core/SkPictureFlat.h b/src/core/SkPictureFlat.h |
index 76120f79cbad892530b2235155062312aa2f3267..ac8e304c0f2c3031885efb09e47d5a6fe00d4913 100644 |
--- a/src/core/SkPictureFlat.h |
+++ b/src/core/SkPictureFlat.h |
@@ -10,17 +10,19 @@ |
//#define SK_DEBUG_SIZE |
-#include "SkChunkAlloc.h" |
#include "SkBitmap.h" |
#include "SkBitmapHeap.h" |
+#include "SkChecksum.h" |
+#include "SkChunkAlloc.h" |
+#include "SkMatrix.h" |
#include "SkOrderedReadBuffer.h" |
#include "SkOrderedWriteBuffer.h" |
-#include "SkPicture.h" |
-#include "SkPtrRecorder.h" |
-#include "SkMatrix.h" |
#include "SkPaint.h" |
#include "SkPath.h" |
+#include "SkPicture.h" |
+#include "SkPtrRecorder.h" |
#include "SkRegion.h" |
+#include "SkTDynamicHash.h" |
#include "SkTRefArray.h" |
#include "SkTSearch.h" |
@@ -263,87 +265,32 @@ private: |
class SkFlatData { |
public: |
- /** |
- * Compare two SkFlatData ptrs, returning -1, 0, 1 to allow them to be |
- * sorted. |
- * |
- * Note: this assumes that a and b have different sentinel values, either |
- * InCache or AsCandidate, otherwise the loop will go beyond the end of |
- * the buffers. |
- * |
- * dataToCompare() returns 2 fields before the flattened data: |
- * - checksum |
- * - size |
- * This ensures that if we see two blocks of different length, we will |
- * notice that right away, and not read any further. It also ensures that |
- * we see the checksum right away, so that most of the time it is enough |
- * to short-circuit our comparison. |
- */ |
- static int Compare(const SkFlatData& a, const SkFlatData& b) { |
- const uint32_t* stop = a.dataStop(); |
- const uint32_t* a_ptr = a.dataToCompare() - 1; |
- const uint32_t* b_ptr = b.dataToCompare() - 1; |
- // We use -1 above, so we can pre-increment our pointers in the loop |
- while (*++a_ptr == *++b_ptr) {} |
- |
- if (a_ptr == stop) { // sentinel |
- SkASSERT(b.dataStop() == b_ptr); |
- return 0; |
- } |
- SkASSERT(a_ptr < a.dataStop()); |
- SkASSERT(b_ptr < b.dataStop()); |
- return (*a_ptr < *b_ptr) ? -1 : 1; |
- } |
- |
- // Adapts Compare to be used with SkTSearch |
- static bool Less(const SkFlatData& a, const SkFlatData& b) { |
- return Compare(a, b) < 0; |
- } |
- |
- int index() const { return fIndex; } |
- const void* data() const { return (const char*)this + sizeof(*this); } |
- void* data() { return (char*)this + sizeof(*this); } |
- // Our data is always 32bit aligned, so we can offer this accessor |
- uint32_t* data32() { return (uint32_t*)this->data(); } |
- // Returns the size of the flattened data. |
- size_t flatSize() const { return fFlatSize; } |
- |
- void setSentinelInCache() { |
- this->setSentinel(kInCache_Sentinel); |
- } |
- void setSentinelAsCandidate() { |
- this->setSentinel(kCandidate_Sentinel); |
- } |
- |
- uint32_t checksum() const { return fChecksum; } |
- |
-#ifdef SK_DEBUG_SIZE |
- // returns the logical size of our data. Does not return any sentinel or |
- // padding we might have. |
- size_t size() const { |
- return sizeof(SkFlatData) + fFlatSize; |
- } |
-#endif |
- |
- static SkFlatData* Create(SkFlatController* controller, const void* obj, int index, |
+ // Flatten obj into an SkFlatData with this index. controller owns the SkFlatData*. |
+ static SkFlatData* Create(SkFlatController* controller, |
+ const void* obj, |
+ int index, |
void (*flattenProc)(SkOrderedWriteBuffer&, const void*)); |
+ // Unflatten this into result, using bitmapHeap and facePlayback for bitmaps and fonts if given. |
void unflatten(void* result, |
void (*unflattenProc)(SkOrderedReadBuffer&, void*), |
SkBitmapHeap* bitmapHeap = NULL, |
SkTypefacePlayback* facePlayback = NULL) const; |
- // When we purge an entry, we want to reuse an old index for the new entry, |
- // so we expose this setter. |
- void setIndex(int index) { fIndex = index; } |
- |
- // for unittesting |
- friend bool operator==(const SkFlatData& a, const SkFlatData& b) { |
- size_t N = (const char*)a.dataStop() - (const char*)a.dataToCompare(); |
- return !memcmp(a.dataToCompare(), b.dataToCompare(), N); |
+ // Do these contain the same data? Ignores index() and topBot(). |
+ bool operator==(const SkFlatData& that) const { |
+ if (this->checksum() != that.checksum() || this->flatSize() != that.flatSize()) { |
+ return false; |
+ } |
+ return memcmp(this->data(), that.data(), this->flatSize()) == 0; |
} |
- // returns true if fTopBot[] has been recorded |
+ int index() const { return fIndex; } |
+ const uint8_t* data() const { return (const uint8_t*)this + sizeof(*this); } |
+ size_t flatSize() const { return fFlatSize; } |
+ uint32_t checksum() const { return fChecksum; } |
+ |
+ // Returns true if fTopBot[] has been recorded. |
bool isTopBotWritten() const { |
return !SkScalarIsNaN(fTopBot[0]); |
} |
@@ -355,54 +302,37 @@ public: |
return fTopBot; |
} |
- // return the topbot[] after it has been recorded |
+ // Return the topbot[] after it has been recorded. |
const SkScalar* topBot() const { |
SkASSERT(this->isTopBotWritten()); |
return fTopBot; |
} |
private: |
- // This is *not* part of the key for search/sort |
- int fIndex; |
+ // For SkTDynamicHash. |
+ static const SkFlatData& Identity(const SkFlatData& flat) { return flat; } |
+ static uint32_t Hash(const SkFlatData& flat) { return flat.checksum(); } |
+ static bool Equal(const SkFlatData& a, const SkFlatData& b) { return a == b; } |
- // Cache of paint's FontMetrics fTop,fBottom |
- // initialied to [NaN,NaN] as a sentinel that they have not been recorded yet |
- // |
- // This is *not* part of the key for search/sort |
- mutable SkScalar fTopBot[2]; |
+ void setIndex(int index) { fIndex = index; } |
+ uint8_t* data() { return (uint8_t*)this + sizeof(*this); } |
- // marks fTopBot[] as unrecorded |
- void setTopBotUnwritten() { |
- this->fTopBot[0] = SK_ScalarNaN; // initial to sentinel values |
+ // This assumes the payload flat data has already been written and does not modify it. |
+ void stampHeader(int index, int32_t size) { |
+ SkASSERT(SkIsAlign4(size)); |
+ fIndex = index; |
+ fFlatSize = size; |
+ fTopBot[0] = SK_ScalarNaN; // Mark as unwritten. |
+ fChecksum = SkChecksum::Compute((uint32_t*)this->data(), size); |
} |
- // From here down is the data we look at in the search/sort. We always begin |
- // with the checksum and then length. |
+ int fIndex; |
+ int32_t fFlatSize; |
uint32_t fChecksum; |
- int32_t fFlatSize; // size of flattened data |
- // uint32_t flattenedData[] |
- // uint32_t sentinelValue |
- |
- const uint32_t* dataToCompare() const { |
- return (const uint32_t*)&fChecksum; |
- } |
- const uint32_t* dataStop() const { |
- SkASSERT(SkIsAlign4(fFlatSize)); |
- return (const uint32_t*)((const char*)this->data() + fFlatSize); |
- } |
- |
- enum { |
- kInCache_Sentinel = 0, |
- kCandidate_Sentinel = ~0U, |
- }; |
- void setSentinel(uint32_t value) { |
- SkASSERT(SkIsAlign4(fFlatSize)); |
- this->data32()[fFlatSize >> 2] = value; |
- } |
+ mutable SkScalar fTopBot[2]; // Cache of FontMetrics fTop, fBottom. Starts as [NaN,?]. |
+ // uint32_t flattenedData[] implicitly hangs off the end. |
- // This does not modify the payload flat data, in case it's already been written. |
- void stampHeaderAndSentinel(int index, int32_t size); |
- template <class T> friend class SkFlatDictionary; // For stampHeaderAndSentinel(). |
+ template <class T> friend class SkFlatDictionary; |
}; |
template <class T> |
@@ -417,11 +347,20 @@ public: |
, fScratchSize(scratchSizeGuess) |
, fScratch(AllocScratch(fScratchSize)) |
, fWriteBuffer(kWriteBufferGrowthBytes) |
- , fWriteBufferReady(false) |
- , fNextIndex(1) { // set to 1 since returning a zero from find() indicates failure |
- sk_bzero(fHash, sizeof(fHash)); |
+ , fWriteBufferReady(false) { |
+ this->reset(); |
+ } |
+ |
+ /** |
+ * Clears the dictionary of all entries. However, it does NOT free the |
+ * memory that was allocated for each entry (that's owned by controller). |
+ */ |
+ void reset() { |
+ fIndexedData.rewind(); |
+ // TODO(mtklein): There's no reason to have the index start from 1. Clean this up. |
// index 0 is always empty since it is used as a signal that find failed |
fIndexedData.push(NULL); |
+ fNextIndex = 1; |
} |
~SkFlatDictionary() { |
@@ -429,26 +368,24 @@ public: |
} |
int count() const { |
- SkASSERT(fIndexedData.count() == fSortedData.count()+1); |
- return fSortedData.count(); |
+ SkASSERT(fIndexedData.count() == fNextIndex); |
+ SkASSERT(fHash.count() == fNextIndex - 1); |
+ return fNextIndex - 1; |
} |
- const SkFlatData* operator[](int index) const { |
- SkASSERT(index >= 0 && index < fSortedData.count()); |
- return fSortedData[index]; |
+ // For testing only. Index is zero-based. |
+ const SkFlatData* operator[](int index) { |
+ return fIndexedData[index+1]; |
} |
/** |
- * Clears the dictionary of all entries. However, it does NOT free the |
- * memory that was allocated for each entry. |
+ * Given an element of type T return its 1-based index in the dictionary. If |
+ * the element wasn't previously in the dictionary it is automatically |
+ * added. |
+ * |
*/ |
- void reset() { |
- fSortedData.reset(); |
- fIndexedData.rewind(); |
- // index 0 is always empty since it is used as a signal that find failed |
- fIndexedData.push(NULL); |
- fNextIndex = 1; |
- sk_bzero(fHash, sizeof(fHash)); |
+ int find(const T& element) { |
+ return this->findAndReturnFlat(element)->index(); |
} |
/** |
@@ -459,76 +396,64 @@ public: |
* the entry in the dictionary, it returns the actual SkFlatData. |
*/ |
const SkFlatData* findAndReplace(const T& element, |
- const SkFlatData* toReplace, bool* added, |
+ const SkFlatData* toReplace, |
+ bool* added, |
bool* replaced) { |
SkASSERT(added != NULL && replaced != NULL); |
- int oldCount = fSortedData.count(); |
- const SkFlatData* flat = this->findAndReturnFlat(element); |
- *added = fSortedData.count() == oldCount + 1; |
- *replaced = false; |
- if (*added && toReplace != NULL) { |
- // First, find the index of the one to replace |
- int indexToReplace = fSortedData.find(toReplace); |
- if (indexToReplace >= 0) { |
- // findAndReturnFlat set the index to fNextIndex and increased |
- // fNextIndex by one. Reuse the index from the one being |
- // replaced and reset fNextIndex to the proper value. |
- int oldIndex = flat->index(); |
- const_cast<SkFlatData*>(flat)->setIndex(toReplace->index()); |
- fIndexedData[toReplace->index()] = flat; |
- fNextIndex--; |
- // Remove from the arrays. |
- fSortedData.remove(indexToReplace); |
- fIndexedData.remove(oldIndex); |
- // Remove from the hash table. |
- int oldHash = ChecksumToHashIndex(toReplace->checksum()); |
- if (fHash[oldHash] == toReplace) { |
- fHash[oldHash] = NULL; |
- } |
- // Delete the actual object. |
- fController->unalloc((void*)toReplace); |
- *replaced = true; |
- SkASSERT(fIndexedData.count() == fSortedData.count()+1); |
- } |
+ |
+ const int oldCount = this->count(); |
+ SkFlatData* flat = this->findAndReturnMutableFlat(element); |
+ *added = this->count() > oldCount; |
+ |
+ // If we don't want to replace anything, we're done. |
+ if (!*added || toReplace == NULL) { |
+ *replaced = false; |
+ return flat; |
} |
- return flat; |
- } |
- /** |
- * Given an element of type T return its 1-based index in the dictionary. If |
- * the element wasn't previously in the dictionary it is automatically |
- * added. |
- * |
- * To make the Compare function fast, we write a sentinel value at the end |
- * of each block. The blocks in our fSortedData[] all have a 0 sentinel. The |
- * newly created block we're comparing against has a -1 in the sentinel. |
- * |
- * This trick allows Compare to always loop until failure. If it fails on |
- * the sentinal value, we know the blocks are equal. |
- */ |
- int find(const T& element) { |
- return this->findAndReturnFlat(element)->index(); |
+ // If we don't have the thing to replace, we're done. |
+ const SkFlatData* found = fHash.find(*toReplace); |
+ if (found == NULL) { |
+ *replaced = false; |
+ return flat; |
+ } |
+ |
+ // findAndReturnMutableFlat gave us index (fNextIndex-1), but we'll use the old one. |
+ fIndexedData.remove(flat->index()); |
+ fNextIndex--; |
+ flat->setIndex(found->index()); |
+ fIndexedData[flat->index()] = flat; |
+ |
+ // findAndReturnMutableFlat already called fHash.add(), so we just clean up the old entry. |
+ fHash.remove(*found); |
+ fController->unalloc((void*)found); |
+ SkASSERT(this->count() == oldCount); |
+ |
+ *replaced = true; |
+ return flat; |
} |
/** |
* Unflatten the objects and return them in SkTRefArray, or return NULL |
- * if there no objects (instead of an empty array). |
+ * if there no objects. Caller takes ownership of result. |
*/ |
SkTRefArray<T>* unflattenToArray() const { |
- int count = fSortedData.count(); |
- SkTRefArray<T>* array = NULL; |
- if (count > 0) { |
- array = SkTRefArray<T>::Create(count); |
- this->unflattenIntoArray(&array->writableAt(0)); |
+ const int count = this->count(); |
+ if (count == 0) { |
+ return NULL; |
+ } |
+ SkTRefArray<T>* array = SkTRefArray<T>::Create(count); |
+ for (int i = 0; i < count; i++) { |
+ this->unflatten(&array->writableAt(i), fIndexedData[i+1]); |
} |
return array; |
} |
/** |
- * Unflatten the specific object at the given index |
+ * Unflatten the specific object at the given index. |
+ * Caller takes ownership of the result. |
*/ |
T* unflatten(int index) const { |
- SkASSERT(fIndexedData.count() == fSortedData.count()+1); |
const SkFlatData* element = fIndexedData[index]; |
SkASSERT(index == element->index()); |
@@ -537,41 +462,12 @@ public: |
return dst; |
} |
+ /** |
+ * Find or insert a flattened version of element into the dictionary. |
+ * Caller does not take ownership of the result. This will not return NULL. |
+ */ |
const SkFlatData* findAndReturnFlat(const T& element) { |
- // Only valid until the next call to resetScratch(). |
- const SkFlatData& scratch = this->resetScratch(element, fNextIndex); |
- |
- // See if we have it in the hash? |
- const int hashIndex = ChecksumToHashIndex(scratch.checksum()); |
- const SkFlatData* candidate = fHash[hashIndex]; |
- if (candidate != NULL && SkFlatData::Compare(scratch, *candidate) == 0) { |
- return candidate; |
- } |
- |
- // See if we have it at all? |
- const int index = SkTSearch<const SkFlatData, SkFlatData::Less>(fSortedData.begin(), |
- fSortedData.count(), |
- &scratch, |
- sizeof(&scratch)); |
- if (index >= 0) { |
- // Found. Update hash before we return. |
- fHash[hashIndex] = fSortedData[index]; |
- return fSortedData[index]; |
- } |
- |
- // We don't have it. Add it. |
- SkFlatData* detached = this->detachScratch(); |
- // detached will live beyond the next call to resetScratch(), but is owned by fController. |
- *fSortedData.insert(~index) = detached; // SkTSearch returned bit-not of where to insert. |
- *fIndexedData.insert(detached->index()) = detached; |
- fHash[hashIndex] = detached; |
- |
- SkASSERT(detached->index() == fNextIndex); |
- SkASSERT(fSortedData.count() == fNextIndex); |
- SkASSERT(fIndexedData.count() == fNextIndex+1); |
- fNextIndex++; |
- |
- return detached; |
+ return this->findAndReturnMutableFlat(element); |
} |
protected: |
@@ -579,10 +475,10 @@ protected: |
void (*fUnflattenProc)(SkOrderedReadBuffer&, void*); |
private: |
- // Layout: [ SkFlatData header, 20 bytes ] [ data ..., 4-byte aligned ] [ sentinel, 4 bytes] |
+ // Layout: [ SkFlatData header, 20 bytes ] [ data ..., 4-byte aligned ] |
static size_t SizeWithPadding(size_t flatDataSize) { |
SkASSERT(SkIsAlign4(flatDataSize)); |
- return sizeof(SkFlatData) + flatDataSize + sizeof(uint32_t); |
+ return sizeof(SkFlatData) + flatDataSize; |
} |
// Allocate a new scratch SkFlatData. Must be sk_freed. |
@@ -605,6 +501,21 @@ private: |
fWriteBufferReady = true; |
} |
+ // As findAndReturnFlat, but returns a mutable pointer for internal use. |
+ SkFlatData* findAndReturnMutableFlat(const T& element) { |
+ // Only valid until the next call to resetScratch(). |
+ const SkFlatData& scratch = this->resetScratch(element, fNextIndex); |
+ |
+ SkFlatData* candidate = fHash.find(scratch); |
+ if (candidate != NULL) return candidate; |
+ |
+ SkFlatData* detached = this->detachScratch(); |
+ fHash.add(detached); |
+ *fIndexedData.insert(fNextIndex) = detached; |
+ fNextIndex++; |
+ return detached; |
+ } |
+ |
// This reference is valid only until the next call to resetScratch() or detachScratch(). |
const SkFlatData& resetScratch(const T& element, int index) { |
this->lazyWriteBufferInit(); |
@@ -628,8 +539,8 @@ private: |
fScratch = larger; |
} |
- // The data is in fScratch now, but we need to stamp its header and trailing sentinel. |
- fScratch->stampHeaderAndSentinel(index, bytesWritten); |
+ // The data is in fScratch now but we need to stamp its header. |
+ fScratch->stampHeader(index, bytesWritten); |
return *fScratch; |
} |
@@ -641,72 +552,36 @@ private: |
const size_t paddedSize = SizeWithPadding(fScratch->flatSize()); |
SkFlatData* detached = (SkFlatData*)fController->allocThrow(paddedSize); |
- // Copy scratch into the new SkFlatData, setting the sentinel for cache storage. |
+ // Copy scratch into the new SkFlatData. |
memcpy(detached, fScratch, paddedSize); |
- detached->setSentinelInCache(); |
// We can now reuse fScratch, and detached will live until fController dies. |
return detached; |
} |
void unflatten(T* dst, const SkFlatData* element) const { |
- element->unflatten(dst, fUnflattenProc, |
+ element->unflatten(dst, |
+ fUnflattenProc, |
fController->getBitmapHeap(), |
fController->getTypefacePlayback()); |
} |
- void unflattenIntoArray(T* array) const { |
- const int count = fSortedData.count(); |
- SkASSERT(fIndexedData.count() == fSortedData.count()+1); |
- const SkFlatData* const* iter = fSortedData.begin(); |
- for (int i = 0; i < count; ++i) { |
- const SkFlatData* element = iter[i]; |
- int index = element->index() - 1; |
- SkASSERT((unsigned)index < (unsigned)count); |
- unflatten(&array[index], element); |
- } |
- } |
- |
+ // All SkFlatData* stored in fIndexedData and fHash are owned by the controller. |
SkAutoTUnref<SkFlatController> fController; |
size_t fScratchSize; // How many bytes fScratch has allocated for data itself. |
SkFlatData* fScratch; // Owned, must be freed with sk_free. |
SkOrderedWriteBuffer fWriteBuffer; |
bool fWriteBufferReady; |
- // SkFlatDictionary has two copies of the data one indexed by the |
- // SkFlatData's index and the other sorted. The sorted data is used |
- // for finding and uniquification while the indexed copy is used |
- // for standard array-style lookups based on the SkFlatData's index |
- // (as in 'unflatten'). |
+ // We map between SkFlatData and a 1-based integer index. |
int fNextIndex; |
+ |
+ // For index -> SkFlatData. fIndexedData[0] is always NULL. |
SkTDArray<const SkFlatData*> fIndexedData; |
- // fSortedData is sorted by checksum/size/data. |
- SkTDArray<const SkFlatData*> fSortedData; |
- |
- enum { |
- // Determined by trying diff values on picture-recording benchmarks |
- // (e.g. PictureRecordBench.cpp), choosing the smallest value that |
- // showed a big improvement. Even better would be to benchmark diff |
- // values on recording representative web-pages or other "real" content. |
- HASH_BITS = 7, |
- HASH_MASK = (1 << HASH_BITS) - 1, |
- HASH_COUNT = 1 << HASH_BITS |
- }; |
- const SkFlatData* fHash[HASH_COUNT]; |
- |
- static int ChecksumToHashIndex(uint32_t checksum) { |
- int n = checksum; |
- if (HASH_BITS < 32) { |
- n ^= n >> 16; |
- } |
- if (HASH_BITS < 16) { |
- n ^= n >> 8; |
- } |
- if (HASH_BITS < 8) { |
- n ^= n >> 4; |
- } |
- return n & HASH_MASK; |
- } |
+ |
+ // For SkFlatData -> cached SkFlatData, which has index(). |
+ SkTDynamicHash<SkFlatData, SkFlatData, |
+ SkFlatData::Identity, SkFlatData::Hash, SkFlatData::Equal> fHash; |
}; |
/////////////////////////////////////////////////////////////////////////////// |