Chromium Code Reviews| Index: src/core/SkRecord.h |
| diff --git a/src/core/SkRecord.h b/src/core/SkRecord.h |
| index fb082949b42a2360ab270fc328625af485cd92ee..a2d102138fee2a945b18a2a85b53e15cafa7de2a 100644 |
| --- a/src/core/SkRecord.h |
| +++ b/src/core/SkRecord.h |
| @@ -13,7 +13,7 @@ |
| #include "SkTemplates.h" |
| #include "SkVarAlloc.h" |
| -// SkRecord (REC-ord) represents a sequence of SkCanvas calls, saved for future use. |
| +// SkRecord represents a sequence of SkCanvas calls, saved for future use. |
| // These future uses may include: replay, optimization, serialization, or combinations of those. |
| // |
| // Though an enterprising user may find calling alloc(), append(), visit(), and mutate() enough to |
| @@ -27,10 +27,15 @@ |
| class SkRecord : public SkNVRefCnt<SkRecord> { |
| enum { |
| - kFirstReserveCount = 64 / sizeof(void*), |
| + // TODO: tune these two constants. |
| + kInlineRecords = 4, // Ideally our lower limit on recorded ops per picture. |
| + kLgInlineAllocBytes = 8, // 256 bytes inline, then SkVarAlloc mallocs starting at 512 bytes. |
|
reed1
2015/04/08 20:34:08
the const says "8 bytes" but the comment says 256
mtklein
2015/04/08 20:36:39
It's lg(inlineAllocBytes).
|
| }; |
| public: |
| - SkRecord() : fCount(0), fReserved(0), fAlloc(8/*start block sizes at 256 bytes*/) {} |
| + SkRecord() |
| + : fCount(0) |
| + , fReserved(kInlineRecords) |
| + , fAlloc(kLgInlineAllocBytes+1, fInlineAlloc, sizeof(fInlineAlloc)) {} |
|
reed1
2015/04/08 20:34:08
Why the +1 ?
mtklein
2015/04/08 20:36:39
*2. We inline 1<<8 bytes, then the SkVarAlloc sta
|
| ~SkRecord(); |
| // Returns the number of canvas commands in this SkRecord. |
| @@ -43,7 +48,7 @@ public: |
| template <typename R, typename F> |
| R visit(unsigned i, F& f) const { |
| SkASSERT(i < this->count()); |
| - return fRecords[i].visit<R>(fTypes[i], f); |
| + return fRecords[i].visit<R>(f); |
| } |
| // Mutate the i-th canvas command with a functor matching this interface: |
| @@ -53,15 +58,15 @@ public: |
| template <typename R, typename F> |
| R mutate(unsigned i, F& f) { |
| SkASSERT(i < this->count()); |
| - return fRecords[i].mutate<R>(fTypes[i], f); |
| + return fRecords[i].mutate<R>(f); |
| } |
| - // TODO: It'd be nice to infer R from F for visit and mutate if we ever get std::result_of. |
| + |
| + // TODO: It'd be nice to infer R from F for visit and mutate. |
| // Allocate contiguous space for count Ts, to be freed when the SkRecord is destroyed. |
| // Here T can be any class, not just those from SkRecords. Throws on failure. |
| template <typename T> |
| T* alloc(size_t count = 1) { |
| - // Bump up to the next pointer width if needed, so all allocations start pointer-aligned. |
| return (T*)fAlloc.alloc(sizeof(T) * count, SK_MALLOC_THROW); |
| } |
| @@ -72,7 +77,6 @@ public: |
| if (fCount == fReserved) { |
| this->grow(); |
| } |
| - fTypes[fCount] = T::kType; |
| return fRecords[fCount++].set(this->allocCommand<T>()); |
| } |
| @@ -86,7 +90,6 @@ public: |
| Destroyer destroyer; |
| this->mutate<void>(i, destroyer); |
| - fTypes[i] = T::kType; |
| return fRecords[i].set(this->allocCommand<T>()); |
| } |
| @@ -97,10 +100,9 @@ public: |
| T* replace(unsigned i, const SkRecords::Adopted<Existing>& proofOfAdoption) { |
| SkASSERT(i < this->count()); |
| - SkASSERT(Existing::kType == fTypes[i]); |
| - SkASSERT(proofOfAdoption == fRecords[i].ptr<Existing>()); |
| + SkASSERT(Existing::kType == fRecords[i].fType); |
| + SkASSERT(proofOfAdoption == (void*)fRecords[i].fPtr); |
| - fTypes[i] = T::kType; |
| return fRecords[i].set(this->allocCommand<T>()); |
| } |
| @@ -109,9 +111,7 @@ public: |
| size_t bytesUsed() const; |
| private: |
| - // Implementation notes! |
| - // |
| - // Logically an SkRecord is structured as an array of pointers into a big chunk of memory where |
| + // An SkRecord is structured as an array of pointers into a big chunk of memory where |
| // records representing each canvas draw call are stored: |
| // |
| // fRecords: [*][*][*]... |
| @@ -123,27 +123,8 @@ private: |
| // v v v |
| // fAlloc: [SkRecords::DrawRect][SkRecords::DrawPosTextH][SkRecords::DrawRect]... |
| // |
| - // In the scheme above, the pointers in fRecords are void*: they have no type. The type is not |
| - // stored in fAlloc either; we just write raw data there. But we need that type information. |
| - // Here are some options: |
| - // 1) use inheritance, virtuals, and vtables to make the fRecords pointers smarter |
| - // 2) store the type data manually in fAlloc at the start of each record |
| - // 3) store the type data manually somewhere with fRecords |
| - // |
| - // This code uses approach 3). The implementation feels very similar to 1), but it's |
| - // devirtualized instead of using the language's polymorphism mechanisms. This lets us work |
| - // with the types themselves (as SkRecords::Type), a sort of limited free RTTI; it lets us pay |
| - // only 1 byte to store the type instead of a full pointer (4-8 bytes); and it leads to better |
| - // decoupling between the SkRecords::* record types and the operations performed on them in |
| - // visit() or mutate(). The recorded canvas calls don't have to have any idea about the |
| - // operations performed on them. |
| - // |
| - // We store the types in a parallel fTypes array, mainly so that they can be tightly packed as |
| - // single bytes. This has the side effect of allowing very fast analysis passes over an |
| - // SkRecord looking for just patterns of draw commands (or using this as a quick reject |
| - // mechanism) though there's admittedly not a very good API exposed publically for this. |
| - // |
| - // The cost to append a T into this structure is 1 + sizeof(void*) + sizeof(T). |
| + // We store the types of each of the pointers alongside the pointer. |
| + // The cost to append a T to this structure is 8 + sizeof(T) bytes. |
| // A mutator that can be used with replace to destroy canvas commands. |
| struct Destroyer { |
| @@ -151,19 +132,6 @@ private: |
| void operator()(T* record) { record->~T(); } |
| }; |
| - // Logically the same as SkRecords::Type, but packed into 8 bits. |
| - struct Type8 { |
| - public: |
| - // This intentionally converts implicitly back and forth. |
| - Type8(SkRecords::Type type) : fType(type) { SkASSERT(*this == type); } |
| - operator SkRecords::Type () { return (SkRecords::Type)fType; } |
| - |
| - private: |
| - uint8_t fType; |
| - }; |
| - |
| - // No point in allocating any more than one of an empty struct. |
| - // We could just return NULL but it's sort of confusing to return NULL on success. |
| template <typename T> |
| SK_WHEN(SkTIsEmpty<T>, T*) allocCommand() { |
| static T singleton = {}; |
| @@ -173,65 +141,55 @@ private: |
| template <typename T> |
| SK_WHEN(!SkTIsEmpty<T>, T*) allocCommand() { return this->alloc<T>(); } |
| - // Called when we've run out of room to record new commands. |
| void grow(); |
| - // An untyped pointer to some bytes in fAlloc. This is the interface for polymorphic dispatch: |
| - // visit() and mutate() work with the parallel fTypes array to do the work of a vtable. |
| + // A typed pointer to some bytes in fAlloc. visit() and mutate() allow polymorphic dispatch. |
| struct Record { |
| - public: |
| // Point this record to its data in fAlloc. Returns ptr for convenience. |
| template <typename T> |
| T* set(T* ptr) { |
| - fPtr = ptr; |
| + fType = T::kType; |
| + fPtr = (uintptr_t)ptr; |
| return ptr; |
| } |
| - // Get the data in fAlloc, assuming it's of type T. |
| - template <typename T> |
| - T* ptr() const { return (T*)fPtr; } |
| - |
| - // Visit this record with functor F (see public API above) assuming the record we're |
| - // pointing to has this type. |
| + // Visit this record with functor F (see public API above). |
| template <typename R, typename F> |
| - R visit(Type8 type, F& f) const { |
| - #define CASE(T) case SkRecords::T##_Type: return f(*this->ptr<SkRecords::T>()); |
| - switch(type) { SK_RECORD_TYPES(CASE) } |
| + R visit(F& f) const { |
| + #define CASE(T) case SkRecords::T##_Type: return f(*(const SkRecords::T*)fPtr); |
| + switch(fType) { SK_RECORD_TYPES(CASE) } |
| #undef CASE |
| SkDEBUGFAIL("Unreachable"); |
| return R(); |
| } |
| - // Mutate this record with functor F (see public API above) assuming the record we're |
| - // pointing to has this type. |
| + // Mutate this record with functor F (see public API above). |
| template <typename R, typename F> |
| - R mutate(Type8 type, F& f) { |
| - #define CASE(T) case SkRecords::T##_Type: return f(this->ptr<SkRecords::T>()); |
| - switch(type) { SK_RECORD_TYPES(CASE) } |
| + R mutate(F& f) { |
| + #define CASE(T) case SkRecords::T##_Type: return f((SkRecords::T*)fPtr); |
| + switch(fType) { SK_RECORD_TYPES(CASE) } |
| #undef CASE |
| SkDEBUGFAIL("Unreachable"); |
| return R(); |
| } |
| - private: |
| - void* fPtr; |
| + // On 32-bit machines we store type in 4 bytes, followed by a pointer. Simple. |
| + // On 64-bit machines we store a pointer with the type slotted into two top (unused) bytes. |
| + // FWIW, SkRecords::Type is tiny. It can easily fit in one byte. |
| + SkRecords::Type fType : sizeof(void*) == 4 ? 32 : 16; |
| + uintptr_t fPtr : sizeof(void*) == 4 ? 32 : 48; |
| }; |
| + static_assert(sizeof(Record) == 8, "Record not packed right."); |
| + |
| + // fRecords needs to be a data structure that can append fixed length data, and need to |
| + // support efficient random access and forward iteration. (It doesn't need to be contiguous.) |
| + unsigned fCount, fReserved; |
| + SkAutoSTMalloc<kInlineRecords, Record> fRecords; |
| // fAlloc needs to be a data structure which can append variable length data in contiguous |
| // chunks, returning a stable handle to that data for later retrieval. |
| - // |
| - // fRecords and fTypes need to be data structures that can append fixed length data, and need to |
| - // support efficient random access and forward iteration. (They don't need to be contiguous.) |
| - |
| - // fCount and fReserved measure both fRecords and fTypes, which always grow in lock step. |
| - unsigned fCount; |
| - unsigned fReserved; |
| - SkAutoTMalloc<Record> fRecords; |
| - SkAutoTMalloc<Type8> fTypes; |
| SkVarAlloc fAlloc; |
| - // Strangely the order of these fields matters. If the unsigneds don't go first we're 56 bytes. |
| - // tomhudson and mtklein have no idea why. |
| + char fInlineAlloc[1 << kLgInlineAllocBytes]; |
| }; |
| -SK_COMPILE_ASSERT(sizeof(SkRecord) <= 56, SkRecordSize); |
| #endif//SkRecord_DEFINED |