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

Side by Side Diff: src/record/SkRecord.h

Issue 206313003: SkRecord strawman (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: IsSmall -> IsLarge: pithier Created 6 years, 8 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 unified diff | Download patch
« no previous file with comments | « gyp/tools.gyp ('k') | src/record/SkRecordDraw.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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
OLDNEW
« no previous file with comments | « gyp/tools.gyp ('k') | src/record/SkRecordDraw.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698