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

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: nits 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 kLgInlineAllocBytes = 8, // 256 bytes inline, then SkVarAlloc mallocs st arting 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).
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(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
34 ~SkRecord(); 39 ~SkRecord();
35 40
36 // Returns the number of canvas commands in this SkRecord. 41 // Returns the number of canvas commands in this SkRecord.
37 unsigned count() const { return fCount; } 42 unsigned count() const { return fCount; }
38 43
39 // Visit the i-th canvas command with a functor matching this interface: 44 // Visit the i-th canvas command with a functor matching this interface:
40 // template <typename T> 45 // template <typename T>
41 // R operator()(const T& record) { ... } 46 // R operator()(const T& record) { ... }
42 // This operator() must be defined for at least all SkRecords::*. 47 // This operator() must be defined for at least all SkRecords::*.
43 template <typename R, typename F> 48 template <typename R, typename F>
44 R visit(unsigned i, F& f) const { 49 R visit(unsigned i, F& f) const {
45 SkASSERT(i < this->count()); 50 SkASSERT(i < this->count());
46 return fRecords[i].visit<R>(fTypes[i], f); 51 return fRecords[i].visit<R>(f);
47 } 52 }
48 53
49 // Mutate the i-th canvas command with a functor matching this interface: 54 // Mutate the i-th canvas command with a functor matching this interface:
50 // template <typename T> 55 // template <typename T>
51 // R operator()(T* record) { ... } 56 // R operator()(T* record) { ... }
52 // This operator() must be defined for at least all SkRecords::*. 57 // This operator() must be defined for at least all SkRecords::*.
53 template <typename R, typename F> 58 template <typename R, typename F>
54 R mutate(unsigned i, F& f) { 59 R mutate(unsigned i, F& f) {
55 SkASSERT(i < this->count()); 60 SkASSERT(i < this->count());
56 return fRecords[i].mutate<R>(fTypes[i], f); 61 return fRecords[i].mutate<R>(f);
57 } 62 }
58 // TODO: It'd be nice to infer R from F for visit and mutate if we ever get std::result_of. 63
64 // TODO: It'd be nice to infer R from F for visit and mutate.
59 65
60 // Allocate contiguous space for count Ts, to be freed when the SkRecord is destroyed. 66 // 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. 67 // Here T can be any class, not just those from SkRecords. Throws on failur e.
62 template <typename T> 68 template <typename T>
63 T* alloc(size_t count = 1) { 69 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); 70 return (T*)fAlloc.alloc(sizeof(T) * count, SK_MALLOC_THROW);
66 } 71 }
67 72
68 // Add a new command of type T to the end of this SkRecord. 73 // 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. 74 // You are expected to placement new an object of type T onto this pointer.
70 template <typename T> 75 template <typename T>
71 T* append() { 76 T* append() {
72 if (fCount == fReserved) { 77 if (fCount == fReserved) {
73 this->grow(); 78 this->grow();
74 } 79 }
75 fTypes[fCount] = T::kType;
76 return fRecords[fCount++].set(this->allocCommand<T>()); 80 return fRecords[fCount++].set(this->allocCommand<T>());
77 } 81 }
78 82
79 // Replace the i-th command with a new command of type T. 83 // 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. 84 // You are expected to placement new an object of type T onto this pointer.
81 // References to the original command are invalidated. 85 // References to the original command are invalidated.
82 template <typename T> 86 template <typename T>
83 T* replace(unsigned i) { 87 T* replace(unsigned i) {
84 SkASSERT(i < this->count()); 88 SkASSERT(i < this->count());
85 89
86 Destroyer destroyer; 90 Destroyer destroyer;
87 this->mutate<void>(i, destroyer); 91 this->mutate<void>(i, destroyer);
88 92
89 fTypes[i] = T::kType;
90 return fRecords[i].set(this->allocCommand<T>()); 93 return fRecords[i].set(this->allocCommand<T>());
91 } 94 }
92 95
93 // Replace the i-th command with a new command of type T. 96 // 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. 97 // 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. 98 // You must show proof that you've already adopted the existing command.
96 template <typename T, typename Existing> 99 template <typename T, typename Existing>
97 T* replace(unsigned i, const SkRecords::Adopted<Existing>& proofOfAdoption) { 100 T* replace(unsigned i, const SkRecords::Adopted<Existing>& proofOfAdoption) {
98 SkASSERT(i < this->count()); 101 SkASSERT(i < this->count());
99 102
100 SkASSERT(Existing::kType == fTypes[i]); 103 SkASSERT(Existing::kType == fRecords[i].fType);
101 SkASSERT(proofOfAdoption == fRecords[i].ptr<Existing>()); 104 SkASSERT(proofOfAdoption == (void*)fRecords[i].fPtr);
102 105
103 fTypes[i] = T::kType;
104 return fRecords[i].set(this->allocCommand<T>()); 106 return fRecords[i].set(this->allocCommand<T>());
105 } 107 }
106 108
107 // Does not return the bytes in any pointers embedded in the Records; caller s 109 // 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. 110 // need to iterate with a visitor to measure those they care for.
109 size_t bytesUsed() const; 111 size_t bytesUsed() const;
110 112
111 private: 113 private:
112 // Implementation notes! 114 // 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: 115 // records representing each canvas draw call are stored:
116 // 116 //
117 // fRecords: [*][*][*]... 117 // fRecords: [*][*][*]...
118 // | | | 118 // | | |
119 // | | | 119 // | | |
120 // | | +---------------------------------------+ 120 // | | +---------------------------------------+
121 // | +-----------------+ | 121 // | +-----------------+ |
122 // | | | 122 // | | |
123 // v v v 123 // v v v
124 // fAlloc: [SkRecords::DrawRect][SkRecords::DrawPosTextH][SkRecords::Draw Rect]... 124 // fAlloc: [SkRecords::DrawRect][SkRecords::DrawPosTextH][SkRecords::Draw Rect]...
125 // 125 //
126 // In the scheme above, the pointers in fRecords are void*: they have no typ e. The type is not 126 // 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. 127 // 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 128
148 // A mutator that can be used with replace to destroy canvas commands. 129 // A mutator that can be used with replace to destroy canvas commands.
149 struct Destroyer { 130 struct Destroyer {
150 template <typename T> 131 template <typename T>
151 void operator()(T* record) { record->~T(); } 132 void operator()(T* record) { record->~T(); }
152 }; 133 };
153 134
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> 135 template <typename T>
168 SK_WHEN(SkTIsEmpty<T>, T*) allocCommand() { 136 SK_WHEN(SkTIsEmpty<T>, T*) allocCommand() {
169 static T singleton = {}; 137 static T singleton = {};
170 return &singleton; 138 return &singleton;
171 } 139 }
172 140
173 template <typename T> 141 template <typename T>
174 SK_WHEN(!SkTIsEmpty<T>, T*) allocCommand() { return this->alloc<T>(); } 142 SK_WHEN(!SkTIsEmpty<T>, T*) allocCommand() { return this->alloc<T>(); }
175 143
176 // Called when we've run out of room to record new commands.
177 void grow(); 144 void grow();
178 145
179 // An untyped pointer to some bytes in fAlloc. This is the interface for po lymorphic dispatch: 146 // 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 { 147 struct Record {
182 public:
183 // Point this record to its data in fAlloc. Returns ptr for convenience . 148 // Point this record to its data in fAlloc. Returns ptr for convenience .
184 template <typename T> 149 template <typename T>
185 T* set(T* ptr) { 150 T* set(T* ptr) {
186 fPtr = ptr; 151 fType = T::kType;
152 fPtr = (uintptr_t)ptr;
187 return ptr; 153 return ptr;
188 } 154 }
189 155
190 // Get the data in fAlloc, assuming it's of type T. 156 // Visit this record with functor F (see public API above).
191 template <typename T>
192 T* ptr() const { return (T*)fPtr; }
193
194 // Visit this record with functor F (see public API above) assuming the record we're
195 // pointing to has this type.
196 template <typename R, typename F> 157 template <typename R, typename F>
197 R visit(Type8 type, F& f) const { 158 R visit(F& f) const {
198 #define CASE(T) case SkRecords::T##_Type: return f(*this->ptr<SkRecords: :T>()); 159 #define CASE(T) case SkRecords::T##_Type: return f(*(const SkRecords::T* )fPtr);
199 switch(type) { SK_RECORD_TYPES(CASE) } 160 switch(fType) { SK_RECORD_TYPES(CASE) }
200 #undef CASE 161 #undef CASE
201 SkDEBUGFAIL("Unreachable"); 162 SkDEBUGFAIL("Unreachable");
202 return R(); 163 return R();
203 } 164 }
204 165
205 // Mutate this record with functor F (see public API above) assuming the record we're 166 // Mutate this record with functor F (see public API above).
206 // pointing to has this type.
207 template <typename R, typename F> 167 template <typename R, typename F>
208 R mutate(Type8 type, F& f) { 168 R mutate(F& f) {
209 #define CASE(T) case SkRecords::T##_Type: return f(this->ptr<SkRecords:: T>()); 169 #define CASE(T) case SkRecords::T##_Type: return f((SkRecords::T*)fPtr);
210 switch(type) { SK_RECORD_TYPES(CASE) } 170 switch(fType) { SK_RECORD_TYPES(CASE) }
211 #undef CASE 171 #undef CASE
212 SkDEBUGFAIL("Unreachable"); 172 SkDEBUGFAIL("Unreachable");
213 return R(); 173 return R();
214 } 174 }
215 175
216 private: 176 // On 32-bit machines we store type in 4 bytes, followed by a pointer. Simple.
217 void* fPtr; 177 // On 64-bit machines we store a pointer with the type slotted into two top (unused) bytes.
178 // FWIW, SkRecords::Type is tiny. It can easily fit in one byte.
179 SkRecords::Type fType : sizeof(void*) == 4 ? 32 : 16;
180 uintptr_t fPtr : sizeof(void*) == 4 ? 32 : 48;
218 }; 181 };
182 static_assert(sizeof(Record) == 8, "Record not packed right.");
183
184 // fRecords needs to be a data structure that can append fixed length data, and need to
185 // support efficient random access and forward iteration. (It doesn't need to be contiguous.)
186 unsigned fCount, fReserved;
187 SkAutoSTMalloc<kInlineRecords, Record> fRecords;
219 188
220 // fAlloc needs to be a data structure which can append variable length data in contiguous 189 // 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. 190 // 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; 191 SkVarAlloc fAlloc;
232 // Strangely the order of these fields matters. If the unsigneds don't go f irst we're 56 bytes. 192 char fInlineAlloc[1 << kLgInlineAllocBytes];
233 // tomhudson and mtklein have no idea why.
234 }; 193 };
235 SK_COMPILE_ASSERT(sizeof(SkRecord) <= 56, SkRecordSize);
236 194
237 #endif//SkRecord_DEFINED 195 #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