Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(143)

Unified Diff: src/core/SkPictureFlat.h

Issue 21564008: use SkTDynamicHash in picture recording (Closed) Base URL: https://skia.googlecode.com/svn/trunk
Patch Set: er, actually, the remove i removed was necessary Created 7 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | src/core/SkPictureFlat.cpp » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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;
};
///////////////////////////////////////////////////////////////////////////////
« no previous file with comments | « no previous file | src/core/SkPictureFlat.cpp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698