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

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

Issue 1061783002: Rearrange SkRecord with small N in mind (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: 32-bit fix Created 5 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 | « no previous file | src/core/SkRecord.cpp » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 /* 1 /*
2 * Copyright 2014 Google Inc. 2 * Copyright 2014 Google Inc.
3 * 3 *
4 * Use of this source code is governed by a BSD-style license that can be 4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file. 5 * found in the LICENSE file.
6 */ 6 */
7 7
8 #ifndef SkRecord_DEFINED 8 #ifndef SkRecord_DEFINED
9 #define SkRecord_DEFINED 9 #define SkRecord_DEFINED
10 10
11 #include "SkRecords.h" 11 #include "SkRecords.h"
12 #include "SkTLogic.h" 12 #include "SkTLogic.h"
13 #include "SkTemplates.h" 13 #include "SkTemplates.h"
14 #include "SkVarAlloc.h" 14 #include "SkVarAlloc.h"
15 15
16 // SkRecord (REC-ord) represents a sequence of SkCanvas calls, saved for future use. 16 // SkRecord represents a sequence of SkCanvas calls, saved for future use.
17 // These future uses may include: replay, optimization, serialization, or combin ations of those. 17 // These future uses may include: replay, optimization, serialization, or combin ations of those.
18 // 18 //
19 // Though an enterprising user may find calling alloc(), append(), visit(), and mutate() enough to 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 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. 21 // for creating an SkRecord, and SkRecordDraw which plays an SkRecord back into another SkCanvas.
22 // 22 //
23 // SkRecord often looks like it's compatible with any type T, but really it's co mpatible with any 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 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 25 // only with SkRecords::* structs defined in SkRecords.h. Your compiler will he lpfully yell if you
26 // get this wrong. 26 // get this wrong.
27 27
28 class SkRecord : public SkNVRefCnt<SkRecord> { 28 class SkRecord : public SkNVRefCnt<SkRecord> {
29 enum { 29 enum {
30 kFirstReserveCount = 64 / sizeof(void*), 30 // TODO: tune these two constants.
31 kInlineRecords = 4, // Ideally our lower limit on recorded ops per picture.
32 kInlineAllocLgBytes = 8, // 1<<8 == 256 bytes inline, then SkVarAlloc st arting at 512 bytes.
31 }; 33 };
32 public: 34 public:
33 SkRecord() : fCount(0), fReserved(0), fAlloc(8/*start block sizes at 256 byt es*/) {} 35 SkRecord()
36 : fCount(0)
37 , fReserved(kInlineRecords)
38 , fAlloc(kInlineAllocLgBytes+1, // First malloc'd block is 2x as large as fInlineAlloc.
39 fInlineAlloc, sizeof(fInlineAlloc)) {}
34 ~SkRecord(); 40 ~SkRecord();
35 41
36 // Returns the number of canvas commands in this SkRecord. 42 // Returns the number of canvas commands in this SkRecord.
37 unsigned count() const { return fCount; } 43 unsigned count() const { return fCount; }
38 44
39 // Visit the i-th canvas command with a functor matching this interface: 45 // Visit the i-th canvas command with a functor matching this interface:
40 // template <typename T> 46 // template <typename T>
41 // R operator()(const T& record) { ... } 47 // R operator()(const T& record) { ... }
42 // This operator() must be defined for at least all SkRecords::*. 48 // This operator() must be defined for at least all SkRecords::*.
43 template <typename R, typename F> 49 template <typename R, typename F>
44 R visit(unsigned i, F& f) const { 50 R visit(unsigned i, F& f) const {
45 SkASSERT(i < this->count()); 51 SkASSERT(i < this->count());
46 return fRecords[i].visit<R>(fTypes[i], f); 52 return fRecords[i].visit<R>(f);
47 } 53 }
48 54
49 // Mutate the i-th canvas command with a functor matching this interface: 55 // Mutate the i-th canvas command with a functor matching this interface:
50 // template <typename T> 56 // template <typename T>
51 // R operator()(T* record) { ... } 57 // R operator()(T* record) { ... }
52 // This operator() must be defined for at least all SkRecords::*. 58 // This operator() must be defined for at least all SkRecords::*.
53 template <typename R, typename F> 59 template <typename R, typename F>
54 R mutate(unsigned i, F& f) { 60 R mutate(unsigned i, F& f) {
55 SkASSERT(i < this->count()); 61 SkASSERT(i < this->count());
56 return fRecords[i].mutate<R>(fTypes[i], f); 62 return fRecords[i].mutate<R>(f);
57 } 63 }
58 // TODO: It'd be nice to infer R from F for visit and mutate if we ever get std::result_of. 64
65 // TODO: It'd be nice to infer R from F for visit and mutate.
59 66
60 // Allocate contiguous space for count Ts, to be freed when the SkRecord is destroyed. 67 // Allocate contiguous space for count Ts, to be freed when the SkRecord is destroyed.
61 // Here T can be any class, not just those from SkRecords. Throws on failur e. 68 // Here T can be any class, not just those from SkRecords. Throws on failur e.
62 template <typename T> 69 template <typename T>
63 T* alloc(size_t count = 1) { 70 T* alloc(size_t count = 1) {
64 // Bump up to the next pointer width if needed, so all allocations start pointer-aligned.
65 return (T*)fAlloc.alloc(sizeof(T) * count, SK_MALLOC_THROW); 71 return (T*)fAlloc.alloc(sizeof(T) * count, SK_MALLOC_THROW);
66 } 72 }
67 73
68 // Add a new command of type T to the end of this SkRecord. 74 // Add a new command of type T to the end of this SkRecord.
69 // You are expected to placement new an object of type T onto this pointer. 75 // You are expected to placement new an object of type T onto this pointer.
70 template <typename T> 76 template <typename T>
71 T* append() { 77 T* append() {
72 if (fCount == fReserved) { 78 if (fCount == fReserved) {
73 this->grow(); 79 this->grow();
74 } 80 }
75 fTypes[fCount] = T::kType;
76 return fRecords[fCount++].set(this->allocCommand<T>()); 81 return fRecords[fCount++].set(this->allocCommand<T>());
77 } 82 }
78 83
79 // Replace the i-th command with a new command of type T. 84 // Replace the i-th command with a new command of type T.
80 // You are expected to placement new an object of type T onto this pointer. 85 // You are expected to placement new an object of type T onto this pointer.
81 // References to the original command are invalidated. 86 // References to the original command are invalidated.
82 template <typename T> 87 template <typename T>
83 T* replace(unsigned i) { 88 T* replace(unsigned i) {
84 SkASSERT(i < this->count()); 89 SkASSERT(i < this->count());
85 90
86 Destroyer destroyer; 91 Destroyer destroyer;
87 this->mutate<void>(i, destroyer); 92 this->mutate<void>(i, destroyer);
88 93
89 fTypes[i] = T::kType;
90 return fRecords[i].set(this->allocCommand<T>()); 94 return fRecords[i].set(this->allocCommand<T>());
91 } 95 }
92 96
93 // Replace the i-th command with a new command of type T. 97 // Replace the i-th command with a new command of type T.
94 // You are expected to placement new an object of type T onto this pointer. 98 // You are expected to placement new an object of type T onto this pointer.
95 // You must show proof that you've already adopted the existing command. 99 // You must show proof that you've already adopted the existing command.
96 template <typename T, typename Existing> 100 template <typename T, typename Existing>
97 T* replace(unsigned i, const SkRecords::Adopted<Existing>& proofOfAdoption) { 101 T* replace(unsigned i, const SkRecords::Adopted<Existing>& proofOfAdoption) {
98 SkASSERT(i < this->count()); 102 SkASSERT(i < this->count());
99 103
100 SkASSERT(Existing::kType == fTypes[i]); 104 SkASSERT(Existing::kType == fRecords[i].type());
101 SkASSERT(proofOfAdoption == fRecords[i].ptr<Existing>()); 105 SkASSERT(proofOfAdoption == fRecords[i].ptr());
102 106
103 fTypes[i] = T::kType;
104 return fRecords[i].set(this->allocCommand<T>()); 107 return fRecords[i].set(this->allocCommand<T>());
105 } 108 }
106 109
107 // Does not return the bytes in any pointers embedded in the Records; caller s 110 // Does not return the bytes in any pointers embedded in the Records; caller s
108 // need to iterate with a visitor to measure those they care for. 111 // need to iterate with a visitor to measure those they care for.
109 size_t bytesUsed() const; 112 size_t bytesUsed() const;
110 113
111 private: 114 private:
112 // Implementation notes! 115 // An SkRecord is structured as an array of pointers into a big chunk of mem ory where
113 //
114 // Logically an SkRecord is structured as an array of pointers into a big ch unk of memory where
115 // records representing each canvas draw call are stored: 116 // records representing each canvas draw call are stored:
116 // 117 //
117 // fRecords: [*][*][*]... 118 // fRecords: [*][*][*]...
118 // | | | 119 // | | |
119 // | | | 120 // | | |
120 // | | +---------------------------------------+ 121 // | | +---------------------------------------+
121 // | +-----------------+ | 122 // | +-----------------+ |
122 // | | | 123 // | | |
123 // v v v 124 // v v v
124 // fAlloc: [SkRecords::DrawRect][SkRecords::DrawPosTextH][SkRecords::Draw Rect]... 125 // fAlloc: [SkRecords::DrawRect][SkRecords::DrawPosTextH][SkRecords::Draw Rect]...
125 // 126 //
126 // In the scheme above, the pointers in fRecords are void*: they have no typ e. The type is not 127 // We store the types of each of the pointers alongside the pointer.
127 // stored in fAlloc either; we just write raw data there. But we need that type information. 128 // The cost to append a T to this structure is 8 + sizeof(T) bytes.
128 // Here are some options:
129 // 1) use inheritance, virtuals, and vtables to make the fRecords pointers smarter
130 // 2) store the type data manually in fAlloc at the start of each record
131 // 3) store the type data manually somewhere with fRecords
132 //
133 // This code uses approach 3). The implementation feels very similar to 1), but it's
134 // devirtualized instead of using the language's polymorphism mechanisms. T his lets us work
135 // with the types themselves (as SkRecords::Type), a sort of limited free RT TI; it lets us pay
136 // only 1 byte to store the type instead of a full pointer (4-8 bytes); and it leads to better
137 // decoupling between the SkRecords::* record types and the operations perfo rmed on them in
138 // visit() or mutate(). The recorded canvas calls don't have to have any id ea about the
139 // operations performed on them.
140 //
141 // We store the types in a parallel fTypes array, mainly so that they can be tightly packed as
142 // single bytes. This has the side effect of allowing very fast analysis pa sses over an
143 // SkRecord looking for just patterns of draw commands (or using this as a q uick reject
144 // mechanism) though there's admittedly not a very good API exposed publical ly for this.
145 //
146 // The cost to append a T into this structure is 1 + sizeof(void*) + sizeof( T).
147 129
148 // A mutator that can be used with replace to destroy canvas commands. 130 // A mutator that can be used with replace to destroy canvas commands.
149 struct Destroyer { 131 struct Destroyer {
150 template <typename T> 132 template <typename T>
151 void operator()(T* record) { record->~T(); } 133 void operator()(T* record) { record->~T(); }
152 }; 134 };
153 135
154 // Logically the same as SkRecords::Type, but packed into 8 bits.
155 struct Type8 {
156 public:
157 // This intentionally converts implicitly back and forth.
158 Type8(SkRecords::Type type) : fType(type) { SkASSERT(*this == type); }
159 operator SkRecords::Type () { return (SkRecords::Type)fType; }
160
161 private:
162 uint8_t fType;
163 };
164
165 // No point in allocating any more than one of an empty struct.
166 // We could just return NULL but it's sort of confusing to return NULL on su ccess.
167 template <typename T> 136 template <typename T>
168 SK_WHEN(SkTIsEmpty<T>, T*) allocCommand() { 137 SK_WHEN(SkTIsEmpty<T>, T*) allocCommand() {
169 static T singleton = {}; 138 static T singleton = {};
170 return &singleton; 139 return &singleton;
171 } 140 }
172 141
173 template <typename T> 142 template <typename T>
174 SK_WHEN(!SkTIsEmpty<T>, T*) allocCommand() { return this->alloc<T>(); } 143 SK_WHEN(!SkTIsEmpty<T>, T*) allocCommand() { return this->alloc<T>(); }
175 144
176 // Called when we've run out of room to record new commands.
177 void grow(); 145 void grow();
178 146
179 // An untyped pointer to some bytes in fAlloc. This is the interface for po lymorphic dispatch: 147 // A typed pointer to some bytes in fAlloc. visit() and mutate() allow poly morphic dispatch.
180 // visit() and mutate() work with the parallel fTypes array to do the work o f a vtable.
181 struct Record { 148 struct Record {
182 public: 149 // On 32-bit machines we store type in 4 bytes, followed by a pointer. Simple.
150 // On 64-bit machines we store a pointer with the type slotted into two top (unused) bytes.
151 // FWIW, SkRecords::Type is tiny. It can easily fit in one byte.
152 uint64_t fTypeAndPtr;
153 static const int kTypeShift = sizeof(void*) == 4 ? 32 : 48;
154
183 // Point this record to its data in fAlloc. Returns ptr for convenience . 155 // Point this record to its data in fAlloc. Returns ptr for convenience .
184 template <typename T> 156 template <typename T>
185 T* set(T* ptr) { 157 T* set(T* ptr) {
186 fPtr = ptr; 158 fTypeAndPtr = ((uint64_t)T::kType) << kTypeShift | (uintptr_t)ptr;
159 SkASSERT(this->ptr() == ptr && this->type() == T::kType);
187 return ptr; 160 return ptr;
188 } 161 }
189 162
190 // Get the data in fAlloc, assuming it's of type T. 163 SkRecords::Type type() const { return (SkRecords::Type)(fTypeAndPtr >> k TypeShift); }
191 template <typename T> 164 void* ptr() const { return (void*)(fTypeAndPtr & ((1ull<<kTypeShift)-1)) ; }
192 T* ptr() const { return (T*)fPtr; }
193 165
194 // Visit this record with functor F (see public API above) assuming the record we're 166 // Visit this record with functor F (see public API above).
195 // pointing to has this type.
196 template <typename R, typename F> 167 template <typename R, typename F>
197 R visit(Type8 type, F& f) const { 168 R visit(F& f) const {
198 #define CASE(T) case SkRecords::T##_Type: return f(*this->ptr<SkRecords: :T>()); 169 #define CASE(T) case SkRecords::T##_Type: return f(*(const SkRecords::T* )this->ptr());
199 switch(type) { SK_RECORD_TYPES(CASE) } 170 switch(this->type()) { SK_RECORD_TYPES(CASE) }
200 #undef CASE 171 #undef CASE
201 SkDEBUGFAIL("Unreachable"); 172 SkDEBUGFAIL("Unreachable");
202 return R(); 173 return R();
203 } 174 }
204 175
205 // Mutate this record with functor F (see public API above) assuming the record we're 176 // Mutate this record with functor F (see public API above).
206 // pointing to has this type.
207 template <typename R, typename F> 177 template <typename R, typename F>
208 R mutate(Type8 type, F& f) { 178 R mutate(F& f) {
209 #define CASE(T) case SkRecords::T##_Type: return f(this->ptr<SkRecords:: T>()); 179 #define CASE(T) case SkRecords::T##_Type: return f((SkRecords::T*)this-> ptr());
210 switch(type) { SK_RECORD_TYPES(CASE) } 180 switch(this->type()) { SK_RECORD_TYPES(CASE) }
211 #undef CASE 181 #undef CASE
212 SkDEBUGFAIL("Unreachable"); 182 SkDEBUGFAIL("Unreachable");
213 return R(); 183 return R();
214 } 184 }
185 };
215 186
216 private: 187 // fRecords needs to be a data structure that can append fixed length data, and need to
217 void* fPtr; 188 // support efficient random access and forward iteration. (It doesn't need to be contiguous.)
218 }; 189 unsigned fCount, fReserved;
190 SkAutoSTMalloc<kInlineRecords, Record> fRecords;
219 191
220 // fAlloc needs to be a data structure which can append variable length data in contiguous 192 // fAlloc needs to be a data structure which can append variable length data in contiguous
221 // chunks, returning a stable handle to that data for later retrieval. 193 // chunks, returning a stable handle to that data for later retrieval.
222 //
223 // fRecords and fTypes need to be data structures that can append fixed leng th data, and need to
224 // support efficient random access and forward iteration. (They don't need to be contiguous.)
225
226 // fCount and fReserved measure both fRecords and fTypes, which always grow in lock step.
227 unsigned fCount;
228 unsigned fReserved;
229 SkAutoTMalloc<Record> fRecords;
230 SkAutoTMalloc<Type8> fTypes;
231 SkVarAlloc fAlloc; 194 SkVarAlloc fAlloc;
232 // Strangely the order of these fields matters. If the unsigneds don't go f irst we're 56 bytes. 195 char fInlineAlloc[1 << kInlineAllocLgBytes];
233 // tomhudson and mtklein have no idea why.
234 }; 196 };
235 SK_COMPILE_ASSERT(sizeof(SkRecord) <= 56, SkRecordSize);
236 197
237 #endif//SkRecord_DEFINED 198 #endif//SkRecord_DEFINED
OLDNEW
« no previous file with comments | « no previous file | src/core/SkRecord.cpp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698