OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright 2014 Google Inc. | |
3 * | |
4 * Use of this source code is governed by a BSD-style license that can be | |
5 * found in the LICENSE file. | |
6 */ | |
7 | |
8 #ifndef SkRecord_DEFINED | |
9 #define SkRecord_DEFINED | |
10 | |
11 #include "SkChunkAlloc.h" | |
12 #include "SkRecords.h" | |
13 #include "SkTLogic.h" | |
14 #include "SkTemplates.h" | |
15 | |
16 // SkRecord (REC-ord) represents a sequence of SkCanvas calls, saved for future
use. | |
17 // These future uses may include: replay, optimization, serialization, or combin
ations of those. | |
18 // | |
19 // Though an enterprising user may find calling alloc(), append(), visit(), and
mutate() enough to | |
20 // work with SkRecord, you probably want to look at SkRecorder which presents an
SkCanvas interface | |
21 // for creating an SkRecord, and SkRecordDraw which plays an SkRecord back into
another SkCanvas. | |
22 // | |
23 // SkRecord often looks like it's compatible with any type T, but really it's co
mpatible with any | |
24 // type T which has a static const SkRecords::Type kType. That is to say, SkRec
ord is compatible | |
25 // only with SkRecords::* structs defined in SkRecords.h. Your compiler will he
lpfully yell if you | |
26 // get this wrong. | |
27 | |
28 class SkRecord : SkNoncopyable { | |
29 public: | |
30 SkRecord(size_t chunkBytes = 4096, unsigned firstReserveCount = 64 / sizeof(
void*)) | |
31 : fAlloc(chunkBytes), fCount(0), fReserved(0), kFirstReserveCount(firstR
eserveCount) {} | |
32 | |
33 ~SkRecord() { | |
34 Destroyer destroyer; | |
35 for (unsigned i = 0; i < this->count(); i++) { | |
36 this->mutate<void>(i, destroyer); | |
37 } | |
38 } | |
39 | |
40 // Returns the number of canvas commands in this SkRecord. | |
41 unsigned count() const { return fCount; } | |
42 | |
43 // Visit the i-th canvas command with a functor matching this interface: | |
44 // template <typename T> | |
45 // R operator()(const T& record) { ... } | |
46 // This operator() must be defined for at least all SkRecords::*. | |
47 template <typename R, typename F> | |
48 R visit(unsigned i, F& f) const { | |
49 SkASSERT(i < this->count()); | |
50 return fRecords[i].visit<R>(fTypes[i], f); | |
51 } | |
52 | |
53 // Mutate the i-th canvas command with a functor matching this interface: | |
54 // template <typename T> | |
55 // R operator()(T* record) { ... } | |
56 // This operator() must be defined for at least all SkRecords::*. | |
57 template <typename R, typename F> | |
58 R mutate(unsigned i, F& f) { | |
59 SkASSERT(i < this->count()); | |
60 return fRecords[i].mutate<R>(fTypes[i], f); | |
61 } | |
62 // TODO: It'd be nice to infer R from F for visit and mutate if we ever get
std::result_of. | |
63 | |
64 // Allocate contiguous space for count Ts, to be freed when the SkRecord is
destroyed. | |
65 // Here T can be any class, not just those from SkRecords. Throws on failur
e. | |
66 template <typename T> | |
67 T* alloc(unsigned count = 1) { | |
68 return (T*)fAlloc.allocThrow(sizeof(T) * count); | |
69 } | |
70 | |
71 // Add a new command of type T to the end of this SkRecord. | |
72 // You are expected to placement new an object of type T onto this pointer. | |
73 template <typename T> | |
74 T* append() { | |
75 if (fCount == fReserved) { | |
76 fReserved = SkTMax(kFirstReserveCount, fReserved*2); | |
77 fRecords.realloc(fReserved); | |
78 fTypes.realloc(fReserved); | |
79 } | |
80 | |
81 fTypes[fCount] = T::kType; | |
82 return fRecords[fCount++].set(this->allocCommand<T>()); | |
83 } | |
84 | |
85 // Replace the i-th command with a new command of type T. | |
86 // You are expected to placement new an object of type T onto this pointer. | |
87 // References to the original command are invalidated. | |
88 template <typename T> | |
89 T* replace(unsigned i) { | |
90 SkASSERT(i < this->count()); | |
91 | |
92 Destroyer destroyer; | |
93 this->mutate<void>(i, destroyer); | |
94 | |
95 fTypes[i] = T::kType; | |
96 return fRecords[i].set(this->allocCommand<T>()); | |
97 } | |
98 | |
99 // Replace the i-th command with a new command of type T. | |
100 // You are expected to placement new an object of type T onto this pointer. | |
101 // You must show proof that you've already adopted the existing command. | |
102 template <typename T, typename Existing> | |
103 T* replace(unsigned i, const SkRecords::Adopted<Existing>& proofOfAdoption)
{ | |
104 SkASSERT(i < this->count()); | |
105 | |
106 SkASSERT(Existing::kType == fTypes[i]); | |
107 SkASSERT(proofOfAdoption == fRecords[i].ptr<Existing>()); | |
108 | |
109 fTypes[i] = T::kType; | |
110 return fRecords[i].set(this->allocCommand<T>()); | |
111 } | |
112 | |
113 private: | |
114 // Implementation notes! | |
115 // | |
116 // Logically an SkRecord is structured as an array of pointers into a big ch
unk of memory where | |
117 // records representing each canvas draw call are stored: | |
118 // | |
119 // fRecords: [*][*][*]... | |
120 // | | | | |
121 // | | | | |
122 // | | +---------------------------------------+ | |
123 // | +-----------------+ | | |
124 // | | | | |
125 // v v v | |
126 // fAlloc: [SkRecords::DrawRect][SkRecords::DrawPosTextH][SkRecords::Draw
Rect]... | |
127 // | |
128 // In the scheme above, the pointers in fRecords are void*: they have no typ
e. The type is not | |
129 // stored in fAlloc either; we just write raw data there. But we need that
type information. | |
130 // Here are some options: | |
131 // 1) use inheritance, virtuals, and vtables to make the fRecords pointers
smarter | |
132 // 2) store the type data manually in fAlloc at the start of each record | |
133 // 3) store the type data manually somewhere with fRecords | |
134 // | |
135 // This code uses approach 3). The implementation feels very similar to 1),
but it's | |
136 // devirtualized instead of using the language's polymorphism mechanisms. T
his lets us work | |
137 // with the types themselves (as SkRecords::Type), a sort of limited free RT
TI; it lets us pay | |
138 // only 1 byte to store the type instead of a full pointer (4-8 bytes); and
it leads to better | |
139 // decoupling between the SkRecords::* record types and the operations perfo
rmed on them in | |
140 // visit() or mutate(). The recorded canvas calls don't have to have any id
ea about the | |
141 // operations performed on them. | |
142 // | |
143 // We store the types in a parallel fTypes array, mainly so that they can be
tightly packed as | |
144 // single bytes. This has the side effect of allowing very fast analysis pa
sses over an | |
145 // SkRecord looking for just patterns of draw commands (or using this as a q
uick reject | |
146 // mechanism) though there's admittedly not a very good API exposed publical
ly for this. | |
147 // | |
148 // The cost to append a T into this structure is 1 + sizeof(void*) + sizeof(
T). | |
149 | |
150 // A mutator that can be used with replace to destroy canvas commands. | |
151 struct Destroyer { | |
152 template <typename T> | |
153 void operator()(T* record) { record->~T(); } | |
154 }; | |
155 | |
156 // Logically the same as SkRecords::Type, but packed into 8 bits. | |
157 struct Type8 { | |
158 public: | |
159 // This intentionally converts implicitly back and forth. | |
160 Type8(SkRecords::Type type) : fType(type) { SkASSERT(*this == type); } | |
161 operator SkRecords::Type () { return (SkRecords::Type)fType; } | |
162 | |
163 private: | |
164 uint8_t fType; | |
165 }; | |
166 | |
167 // No point in allocating any more than one of an empty struct. | |
168 // We could just return NULL but it's sort of confusing to return NULL on su
ccess. | |
169 template <typename T> | |
170 SK_WHEN(SkTIsEmpty<T>, T*) allocCommand() { | |
171 static T singleton = {}; | |
172 return &singleton; | |
173 } | |
174 | |
175 template <typename T> | |
176 SK_WHEN(!SkTIsEmpty<T>, T*) allocCommand() { return this->alloc<T>(); } | |
177 | |
178 // An untyped pointer to some bytes in fAlloc. This is the interface for po
lymorphic dispatch: | |
179 // visit() and mutate() work with the parallel fTypes array to do the work o
f a vtable. | |
180 struct Record { | |
181 public: | |
182 // Point this record to its data in fAlloc. Returns ptr for convenience
. | |
183 template <typename T> | |
184 T* set(T* ptr) { | |
185 fPtr = ptr; | |
186 return ptr; | |
187 } | |
188 | |
189 // Get the data in fAlloc, assuming it's of type T. | |
190 template <typename T> | |
191 T* ptr() const { return (T*)fPtr; } | |
192 | |
193 // Visit this record with functor F (see public API above) assuming the
record we're | |
194 // pointing to has this type. | |
195 template <typename R, typename F> | |
196 R visit(Type8 type, F& f) const { | |
197 #define CASE(T) case SkRecords::T##_Type: return f(*this->ptr<SkRecords:
:T>()); | |
198 switch(type) { SK_RECORD_TYPES(CASE) } | |
199 #undef CASE | |
200 SkDEBUGFAIL("Unreachable"); | |
201 return R(); | |
202 } | |
203 | |
204 // Mutate this record with functor F (see public API above) assuming the
record we're | |
205 // pointing to has this type. | |
206 template <typename R, typename F> | |
207 R mutate(Type8 type, F& f) { | |
208 #define CASE(T) case SkRecords::T##_Type: return f(this->ptr<SkRecords::
T>()); | |
209 switch(type) { SK_RECORD_TYPES(CASE) } | |
210 #undef CASE | |
211 SkDEBUGFAIL("Unreachable"); | |
212 return R(); | |
213 } | |
214 | |
215 private: | |
216 void* fPtr; | |
217 }; | |
218 | |
219 // fAlloc needs to be a data structure which can append variable length data
in contiguous | |
220 // chunks, returning a stable handle to that data for later retrieval. | |
221 // | |
222 // fRecords and fTypes need to be data structures that can append fixed leng
th data, and need to | |
223 // support efficient forward iteration. (They don't need to be contiguous o
r indexable.) | |
224 | |
225 SkChunkAlloc fAlloc; | |
226 SkAutoTMalloc<Record> fRecords; | |
227 SkAutoTMalloc<Type8> fTypes; | |
228 // fCount and fReserved measure both fRecords and fTypes, which always grow
in lock step. | |
229 unsigned fCount; | |
230 unsigned fReserved; | |
231 const unsigned kFirstReserveCount; | |
232 }; | |
233 | |
234 #endif//SkRecord_DEFINED | |
OLD | NEW |