OLD | NEW |
(Empty) | |
| 1 #ifndef SkRecord_DEFINED |
| 2 #define SkRecord_DEFINED |
| 3 |
| 4 #include "SkChunkAlloc.h" |
| 5 #include "SkRecords.h" |
| 6 #include "SkTemplates.h" |
| 7 |
| 8 // SkRecord (REC-ord) represents a sequence of SkCanvas calls, saved for future
use. |
| 9 // These future uses may include: replay, optimization, serialization, or combin
ations of those. |
| 10 // |
| 11 // Though an enterprising user may find calling alloc(), append(), visit(), and
mutate() enough to |
| 12 // work with SkRecord, you probably want to look at SkRecorder which presents an
SkCanvas interface |
| 13 // for creating an SkRecord, and SkRecordDraw which plays an SkRecord back into
another SkCanvas. |
| 14 // |
| 15 // SkRecord often looks like it's compatible with any type T, but really it's co
mpatible with any |
| 16 // type T which has a static const SkRecords::Type kType. That is to say, SkRec
ord is compatible |
| 17 // only with SkRecords::* structs defined in SkRecords.h. Your compiler will he
lpfully yell if you |
| 18 // get this wrong. |
| 19 |
| 20 class SkRecord : SkNoncopyable { |
| 21 public: |
| 22 SkRecord(size_t chunkBytes = 4096, unsigned firstReserveCount = 64 / sizeof(
void*)) |
| 23 : fAlloc(chunkBytes), fCount(0), fReserved(0), kFirstReserveCount(firstR
eserveCount) {} |
| 24 ~SkRecord() { this->mutate(Destroyer()); } |
| 25 |
| 26 // Accepts a visitor functor with this interface: |
| 27 // template <typename T> |
| 28 // void operator()()(const T& record) { ... } |
| 29 // This operator() must be defined for at least all SkRecords::*; your compi
ler will help you |
| 30 // get this right. |
| 31 // |
| 32 // f will be called on each recorded canvas call in the order they were appe
nd()ed. |
| 33 template <typename F> |
| 34 void visit(F f) const { |
| 35 for (unsigned i = 0; i < fCount; i++) { |
| 36 fRecords[i].visit(fTypes[i], f); |
| 37 } |
| 38 } |
| 39 |
| 40 // Accepts a visitor functor with this interface: |
| 41 // template <typename T> |
| 42 // void operator()()(T* record) { ... } |
| 43 // This operator() must be defined for at least all SkRecords::*; again, you
r compiler will help |
| 44 // you get this right. |
| 45 // |
| 46 // f will be called on each recorded canvas call in the order they were appe
nd()ed. |
| 47 template <typename F> |
| 48 void mutate(F f) { |
| 49 for (unsigned i = 0; i < fCount; i++) { |
| 50 fRecords[i].mutate(fTypes[i], f); |
| 51 } |
| 52 } |
| 53 |
| 54 // Allocate contiguous space for count Ts, to be destroyed (not just freed)
when the SkRecord is |
| 55 // destroyed. For classes with constructors, placement new into this array.
Throws on failure. |
| 56 // Here T can really be any class, not just those from SkRecords. |
| 57 template <typename T> |
| 58 T* alloc(unsigned count = 1) { |
| 59 return (T*)fAlloc.allocThrow(sizeof(T) * count); |
| 60 } |
| 61 |
| 62 // Allocate space to record a canvas call of type T at the end of this SkRec
ord. You are |
| 63 // expected to placement new an object of type T onto this pointer. |
| 64 template <typename T> |
| 65 T* append() { |
| 66 if (fCount == fReserved) { |
| 67 fReserved = SkTMax(kFirstReserveCount, fReserved*2); |
| 68 fRecords.realloc(fReserved); |
| 69 fTypes.realloc(fReserved); |
| 70 } |
| 71 |
| 72 fTypes[fCount] = T::kType; |
| 73 return fRecords[fCount++].alloc<T>(this); |
| 74 } |
| 75 |
| 76 private: |
| 77 // Implementation notes! |
| 78 // |
| 79 // Logically an SkRecord is structured as an array of pointers into a big ch
unk of memory where |
| 80 // records representing each canvas draw call are stored: |
| 81 // |
| 82 // fRecords: [*][*][*]... |
| 83 // | | | |
| 84 // | | | |
| 85 // | | +---------------------------------------+ |
| 86 // | +-----------------+ | |
| 87 // | | | |
| 88 // v v v |
| 89 // fAlloc: [SkRecords::DrawRect][SkRecords::DrawPosTextH][SkRecords::Draw
Rect]... |
| 90 // |
| 91 // In the scheme above, the pointers in fRecords are void*: they have no typ
e. The type is not |
| 92 // stored in fAlloc either; we just write raw data there. But we need that
type information. |
| 93 // Here are some options: |
| 94 // 1) use inheritance, virtuals, and vtables to make the fRecords pointers
smarter |
| 95 // 2) store the type data manually in fAlloc at the start of each record |
| 96 // 3) store the type data manually somewhere with fRecords |
| 97 // |
| 98 // This code uses approach 3). The implementation feels very similar to 1),
but it's |
| 99 // devirtualized instead of using the language's polymorphism mechanisms. T
his lets us work |
| 100 // with the types themselves (as SkRecords::Type), a sort of limited free RT
TI; it lets us pay |
| 101 // only 1 byte to store the type instead of a full pointer (4-8 bytes); and
it leads to better |
| 102 // decoupling between the SkRecords::* record types and the operations perfo
rmed on them in |
| 103 // visit() or mutate(). The recorded canvas calls don't have to have any id
ea about the |
| 104 // operations performed on them. |
| 105 // |
| 106 // We store the types in a parallel fTypes array, mainly so that they can be
tightly packed as |
| 107 // single bytes. This has the side effect of allowing very fast analysis pa
sses over an |
| 108 // SkRecord looking for just patterns of draw commands (or using this as a q
uick reject |
| 109 // mechanism) though there's admittedly not a very good API exposed publical
ly for this. |
| 110 // |
| 111 // We pull one final sneaky trick in the implementation. When recording can
vas calls that need |
| 112 // to store less than a pointer of data, we don't go through the usual path
of allocating the |
| 113 // draw command in fAlloc and a pointer to it in fRecords; instead, we ignor
e fAlloc and |
| 114 // directly allocate the object in the space we would have put the pointer i
n fRecords. This is |
| 115 // why you'll see uintptr_t instead of void* in Record below. |
| 116 // |
| 117 // The cost of appending a single record into this structure is then: |
| 118 // - 1 + sizeof(void*) + sizeof(T) if sizeof(T) > sizeof(void*) |
| 119 // - 1 + sizeof(void*) if sizeof(T) <= sizeof(void*) |
| 120 |
| 121 |
| 122 // A mutator that calls destructors of all the canvas calls we've recorded. |
| 123 struct Destroyer { |
| 124 template <typename T> |
| 125 void operator()(T* record) { record->~T(); } |
| 126 }; |
| 127 |
| 128 // Logically the same as SkRecords::Type, but packed into 8 bits. |
| 129 struct Type8 { |
| 130 public: |
| 131 // This intentionally converts implicitly back and forth. |
| 132 Type8(SkRecords::Type type) : fType(type) { SkASSERT(*this == type); } |
| 133 operator SkRecords::Type () { return (SkRecords::Type)fType; } |
| 134 |
| 135 private: |
| 136 uint8_t fType; |
| 137 }; |
| 138 |
| 139 // Logically a void* to some bytes in fAlloc, but maybe has the bytes stored
immediately |
| 140 // instead. This is also the main interface for devirtualized polymorphic d
ispatch: see visit() |
| 141 // and mutate(), which essentially do the work of the missing vtable. |
| 142 struct Record { |
| 143 public: |
| 144 |
| 145 // Allocate space for a T, perhaps using the SkRecord to allocate that s
pace. |
| 146 template <typename T> |
| 147 T* alloc(SkRecord* record) { |
| 148 if (IsLarge<T>()) { |
| 149 fRecord = (uintptr_t)record->alloc<T>(); |
| 150 } |
| 151 return this->ptr<T>(); |
| 152 } |
| 153 |
| 154 // Visit this record with functor F (see public API above) assuming the
record we're |
| 155 // pointing to has this type. |
| 156 template <typename F> |
| 157 void visit(Type8 type, F f) const { |
| 158 #define CASE(T) case SkRecords::T##_Type: return f(*this->ptr<SkRecords:
:T>()); |
| 159 switch(type) { SK_RECORD_TYPES(CASE) } |
| 160 #undef CASE |
| 161 } |
| 162 |
| 163 // Mutate this record with functor F (see public API above) assuming the
record we're |
| 164 // pointing to has this type. |
| 165 template <typename F> |
| 166 void mutate(Type8 type, F f) { |
| 167 #define CASE(T) case SkRecords::T##_Type: return f(this->ptr<SkRecords::
T>()); |
| 168 switch(type) { SK_RECORD_TYPES(CASE) } |
| 169 #undef CASE |
| 170 } |
| 171 |
| 172 private: |
| 173 template <typename T> |
| 174 T* ptr() const { return (T*)(IsLarge<T>() ? (void*)fRecord : &fRecord);
} |
| 175 |
| 176 // Is T too big to fit directly into a uintptr_t, neededing external all
ocation? |
| 177 template <typename T> |
| 178 static bool IsLarge() { return sizeof(T) > sizeof(uintptr_t); } |
| 179 |
| 180 uintptr_t fRecord; |
| 181 }; |
| 182 |
| 183 // fAlloc needs to be a data structure which can append variable length data
in contiguous |
| 184 // chunks, returning a stable handle to that data for later retrieval. |
| 185 // |
| 186 // fRecords and fTypes need to be data structures that can append fixed leng
th data, and need to |
| 187 // support efficient forward iteration. (They don't need to be contiguous o
r indexable.) |
| 188 |
| 189 SkChunkAlloc fAlloc; |
| 190 SkAutoTMalloc<Record> fRecords; |
| 191 SkAutoTMalloc<Type8> fTypes; |
| 192 // fCount and fReserved measure both fRecords and fTypes, which always grow
in lock step. |
| 193 unsigned fCount; |
| 194 unsigned fReserved; |
| 195 const unsigned kFirstReserveCount; |
| 196 }; |
| 197 |
| 198 #endif//SkRecord_DEFINED |
OLD | NEW |