OLD | NEW |
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 #include "SkRecordOpts.h" | 8 #include "SkRecordOpts.h" |
9 | 9 |
10 #include "SkRecordTraits.h" | 10 #include "SkRecordPattern.h" |
11 #include "SkRecords.h" | 11 #include "SkRecords.h" |
12 #include "SkTDArray.h" | 12 #include "SkTDArray.h" |
13 | 13 |
| 14 using namespace SkRecords; |
| 15 |
14 void SkRecordOptimize(SkRecord* record) { | 16 void SkRecordOptimize(SkRecord* record) { |
15 // TODO(mtklein): fuse independent optimizations to reduce number of passes? | 17 // TODO(mtklein): fuse independent optimizations to reduce number of passes? |
16 SkRecordNoopSaveRestores(record); | 18 SkRecordNoopSaveRestores(record); |
17 SkRecordAnnotateCullingPairs(record); | 19 SkRecordAnnotateCullingPairs(record); |
18 SkRecordReduceDrawPosTextStrength(record); // Helpful to run this before Bo
undDrawPosTextH. | 20 SkRecordReduceDrawPosTextStrength(record); // Helpful to run this before Bo
undDrawPosTextH. |
19 SkRecordBoundDrawPosTextH(record); | 21 SkRecordBoundDrawPosTextH(record); |
20 } | 22 } |
21 | 23 |
22 namespace { | 24 // Most of the optimizations in this file are pattern-based. These are all defi
ned as structs with: |
| 25 // - a Pattern typedef |
| 26 // - a bool onMatch(SkRceord*, Pattern*, unsigned begin, unsigned end) method, |
| 27 // which returns true if it made changes and false if not. |
23 | 28 |
24 // Convenience base class to share some common implementation code. | 29 // Run a pattern-based optimization once across the SkRecord, returning true if
it made any changes. |
25 class Common : SkNoncopyable { | 30 // It looks for spans which match Pass::Pattern, and when found calls onMatch()
with the pattern, |
26 public: | 31 // record, and [begin,end) span of the commands that matched. |
27 explicit Common(SkRecord* record) : fRecord(record), fIndex(0) {} | 32 template <typename Pass> |
| 33 static bool apply(Pass* pass, SkRecord* record) { |
| 34 typename Pass::Pattern pattern; |
| 35 bool changed = false; |
| 36 unsigned begin, end = 0; |
28 | 37 |
29 unsigned index() const { return fIndex; } | 38 while (pattern.search(record, &begin, &end)) { |
30 void next() { ++fIndex; } | 39 changed |= pass->onMatch(record, &pattern, begin, end); |
31 | 40 } |
32 protected: | 41 return changed; |
33 SkRecord* fRecord; | 42 } |
34 unsigned fIndex; | |
35 }; | |
36 | 43 |
37 // Turns logical no-op Save-[non-drawing command]*-Restore patterns into actual
no-ops. | 44 // Turns logical no-op Save-[non-drawing command]*-Restore patterns into actual
no-ops. |
38 // TODO(mtklein): state machine diagram | 45 struct SaveRestoreNooper { |
39 class SaveRestoreNooper : public Common { | 46 // Star matches greedily, so we also have to exclude Save and Restore. |
40 public: | 47 typedef Pattern3<Is<Save>, |
41 explicit SaveRestoreNooper(SkRecord* record) | 48 Star<Not<Or3<Is<Save>, |
42 : Common(record), fSave(kInactive), fChanged(false) {} | 49 Is<Restore>, |
| 50 IsDraw> > >, |
| 51 Is<Restore> > |
| 52 Pattern; |
43 | 53 |
44 // Drawing commands reset state to inactive without nooping. | 54 bool onMatch(SkRecord* record, Pattern* pattern, unsigned begin, unsigned en
d) { |
45 template <typename T> | 55 // If restore doesn't revert both matrix and clip, this isn't safe to no
op away. |
46 SK_WHEN(SkRecords::IsDraw<T>, void) operator()(T*) { fSave = kInactive; } | 56 if (pattern->first<Save>()->flags != SkCanvas::kMatrixClip_SaveFlag) { |
| 57 return false; |
| 58 } |
47 | 59 |
48 // Most non-drawing commands can be ignored. | 60 // The entire span between Save and Restore (inclusively) does nothing. |
49 template <typename T> | 61 for (unsigned i = begin; i < end; i++) { |
50 SK_WHEN(!SkRecords::IsDraw<T>, void) operator()(T*) {} | 62 record->replace<NoOp>(i); |
51 | 63 } |
52 void operator()(SkRecords::Save* r) { | 64 return true; |
53 fSave = SkCanvas::kMatrixClip_SaveFlag == r->flags ? this->index() : kIn
active; | |
54 } | 65 } |
55 | |
56 void operator()(SkRecords::Restore* r) { | |
57 if (fSave != kInactive) { | |
58 // Remove everything between the save and restore, inclusive on both
sides. | |
59 fChanged = true; | |
60 for (unsigned i = fSave; i <= this->index(); i++) { | |
61 fRecord->replace<SkRecords::NoOp>(i); | |
62 } | |
63 fSave = kInactive; | |
64 } | |
65 } | |
66 | |
67 bool changed() const { return fChanged; } | |
68 | |
69 private: | |
70 static const unsigned kInactive = ~0; | |
71 unsigned fSave; | |
72 bool fChanged; | |
73 }; | 66 }; |
74 | 67 void SkRecordNoopSaveRestores(SkRecord* record) { |
75 // Tries to replace PushCull with PairedPushCull, which lets us skip to the pair
ed PopCull | 68 SaveRestoreNooper pass; |
76 // when the canvas can quickReject the cull rect. | 69 while (apply(&pass, record)); // Run until it stops changing things. |
77 class CullAnnotator : public Common { | 70 } |
78 public: | |
79 explicit CullAnnotator(SkRecord* record) : Common(record) {} | |
80 | |
81 // Do nothing to most ops. | |
82 template <typename T> void operator()(T*) {} | |
83 | |
84 void operator()(SkRecords::PushCull* push) { | |
85 Pair pair = { this->index(), push }; | |
86 fPushStack.push(pair); | |
87 } | |
88 | |
89 void operator()(SkRecords::PopCull* pop) { | |
90 Pair push = fPushStack.top(); | |
91 fPushStack.pop(); | |
92 | |
93 SkASSERT(this->index() > push.index); | |
94 unsigned skip = this->index() - push.index; | |
95 | |
96 SkRecords::Adopted<SkRecords::PushCull> adopted(push.command); | |
97 SkNEW_PLACEMENT_ARGS(fRecord->replace<SkRecords::PairedPushCull>(push.in
dex, adopted), | |
98 SkRecords::PairedPushCull, (&adopted, skip)); | |
99 } | |
100 | |
101 private: | |
102 struct Pair { | |
103 unsigned index; | |
104 SkRecords::PushCull* command; | |
105 }; | |
106 | |
107 SkTDArray<Pair> fPushStack; | |
108 }; | |
109 | 71 |
110 // Replaces DrawPosText with DrawPosTextH when all Y coordinates are equal. | 72 // Replaces DrawPosText with DrawPosTextH when all Y coordinates are equal. |
111 class StrengthReducer : public Common { | 73 struct StrengthReducer { |
112 public: | 74 typedef Pattern1<Is<DrawPosText> > Pattern; |
113 explicit StrengthReducer(SkRecord* record) : Common(record) {} | |
114 | 75 |
115 // Do nothing to most ops. | 76 bool onMatch(SkRecord* record, Pattern* pattern, unsigned begin, unsigned en
d) { |
116 template <typename T> void operator()(T*) {} | 77 SkASSERT(end == begin + 1); |
| 78 DrawPosText* draw = pattern->first<DrawPosText>(); |
117 | 79 |
118 void operator()(SkRecords::DrawPosText* r) { | 80 const unsigned points = draw->paint.countText(draw->text, draw->byteLeng
th); |
119 const unsigned points = r->paint.countText(r->text, r->byteLength); | |
120 if (points == 0) { | 81 if (points == 0) { |
121 // No point (ha!). | 82 return false; // No point (ha!). |
122 return; | |
123 } | 83 } |
124 | 84 |
125 const SkScalar firstY = r->pos[0].fY; | 85 const SkScalar firstY = draw->pos[0].fY; |
126 for (unsigned i = 1; i < points; i++) { | 86 for (unsigned i = 1; i < points; i++) { |
127 if (r->pos[i].fY != firstY) { | 87 if (draw->pos[i].fY != firstY) { |
128 // Needs the full strength of DrawPosText. | 88 return false; // Needs full power of DrawPosText. |
129 return; | |
130 } | 89 } |
131 } | 90 } |
132 // All ys are the same. We can replace DrawPosText with DrawPosTextH. | 91 // All ys are the same. We can replace DrawPosText with DrawPosTextH. |
133 | 92 |
134 // r->pos is points SkPoints, [(x,y),(x,y),(x,y),(x,y), ... ]. | 93 // draw->pos is points SkPoints, [(x,y),(x,y),(x,y),(x,y), ... ]. |
135 // We're going to squint and look at that as 2*points SkScalars, [x,y,x,
y,x,y,x,y, ...]. | 94 // We're going to squint and look at that as 2*points SkScalars, [x,y,x,
y,x,y,x,y, ...]. |
136 // Then we'll rearrange things so all the xs are in order up front, clob
bering the ys. | 95 // Then we'll rearrange things so all the xs are in order up front, clob
bering the ys. |
137 SK_COMPILE_ASSERT(sizeof(SkPoint) == 2 * sizeof(SkScalar), SquintingIsNo
tSafe); | 96 SK_COMPILE_ASSERT(sizeof(SkPoint) == 2 * sizeof(SkScalar), SquintingIsNo
tSafe); |
138 SkScalar* scalars = &r->pos[0].fX; | 97 SkScalar* scalars = &draw->pos[0].fX; |
139 for (unsigned i = 0; i < 2*points; i += 2) { | 98 for (unsigned i = 0; i < 2*points; i += 2) { |
140 scalars[i/2] = scalars[i]; | 99 scalars[i/2] = scalars[i]; |
141 } | 100 } |
142 | 101 |
143 // Extend lifetime of r to the end of the method so we can copy its part
s. | 102 // Extend lifetime of draw to the end of the loop so we can copy its pai
nt. |
144 SkRecords::Adopted<SkRecords::DrawPosText> adopted(r); | 103 Adopted<DrawPosText> adopted(draw); |
145 SkNEW_PLACEMENT_ARGS(fRecord->replace<SkRecords::DrawPosTextH>(this->ind
ex(), adopted), | 104 SkNEW_PLACEMENT_ARGS(record->replace<DrawPosTextH>(begin, adopted), |
146 SkRecords::DrawPosTextH, | 105 DrawPosTextH, |
147 (r->text, r->byteLength, scalars, firstY, r->paint)
); | 106 (draw->text, draw->byteLength, scalars, firstY, dra
w->paint)); |
| 107 return true; |
148 } | 108 } |
149 }; | 109 }; |
| 110 void SkRecordReduceDrawPosTextStrength(SkRecord* record) { |
| 111 StrengthReducer pass; |
| 112 apply(&pass, record); |
| 113 } |
150 | 114 |
151 // Tries to replace DrawPosTextH with BoundedDrawPosTextH, which knows conservat
ive upper and lower | 115 // Tries to replace DrawPosTextH with BoundedDrawPosTextH, which knows conservat
ive upper and lower |
152 // bounds to use with SkCanvas::quickRejectY. | 116 // bounds to use with SkCanvas::quickRejectY. |
153 class TextBounder : public Common { | 117 struct TextBounder { |
154 public: | 118 typedef Pattern1<Is<DrawPosTextH> > Pattern; |
155 explicit TextBounder(SkRecord* record) : Common(record) {} | |
156 | 119 |
157 // Do nothing to most ops. | 120 bool onMatch(SkRecord* record, Pattern* pattern, unsigned begin, unsigned en
d) { |
158 template <typename T> void operator()(T*) {} | 121 SkASSERT(end == begin + 1); |
| 122 DrawPosTextH* draw = pattern->first<DrawPosTextH>(); |
159 | 123 |
160 void operator()(SkRecords::DrawPosTextH* r) { | |
161 // If we're drawing vertical text, none of the checks we're about to do
make any sense. | 124 // If we're drawing vertical text, none of the checks we're about to do
make any sense. |
162 // We'll need to call SkPaint::computeFastBounds() later, so bail if tha
t's not possible. | 125 // We'll need to call SkPaint::computeFastBounds() later, so bail if tha
t's not possible. |
163 if (r->paint.isVerticalText() || !r->paint.canComputeFastBounds()) { | 126 if (draw->paint.isVerticalText() || !draw->paint.canComputeFastBounds())
{ |
164 return; | 127 return false; |
165 } | 128 } |
166 | 129 |
167 // Rather than checking the top and bottom font metrics, we guess. Actu
ally looking up the | 130 // Rather than checking the top and bottom font metrics, we guess. Actu
ally looking up the |
168 // top and bottom metrics is slow, and this overapproximation should be
good enough. | 131 // top and bottom metrics is slow, and this overapproximation should be
good enough. |
169 const SkScalar buffer = r->paint.getTextSize() * 1.5f; | 132 const SkScalar buffer = draw->paint.getTextSize() * 1.5f; |
170 SkDEBUGCODE(SkPaint::FontMetrics metrics;) | 133 SkDEBUGCODE(SkPaint::FontMetrics metrics;) |
171 SkDEBUGCODE(r->paint.getFontMetrics(&metrics);) | 134 SkDEBUGCODE(draw->paint.getFontMetrics(&metrics);) |
172 SkASSERT(-buffer <= metrics.fTop); | 135 SkASSERT(-buffer <= metrics.fTop); |
173 SkASSERT(+buffer >= metrics.fBottom); | 136 SkASSERT(+buffer >= metrics.fBottom); |
174 | 137 |
175 // Let the paint adjust the text bounds. We don't care about left and r
ight here, so we use | 138 // Let the paint adjust the text bounds. We don't care about left and r
ight here, so we use |
176 // 0 and 1 respectively just so the bounds rectangle isn't empty. | 139 // 0 and 1 respectively just so the bounds rectangle isn't empty. |
177 SkRect bounds; | 140 SkRect bounds; |
178 bounds.set(0, r->y - buffer, SK_Scalar1, r->y + buffer); | 141 bounds.set(0, draw->y - buffer, SK_Scalar1, draw->y + buffer); |
179 SkRect adjusted = r->paint.computeFastBounds(bounds, &bounds); | 142 SkRect adjusted = draw->paint.computeFastBounds(bounds, &bounds); |
180 | 143 |
181 SkRecords::Adopted<SkRecords::DrawPosTextH> adopted(r); | 144 Adopted<DrawPosTextH> adopted(draw); |
182 SkNEW_PLACEMENT_ARGS( | 145 SkNEW_PLACEMENT_ARGS(record->replace<BoundedDrawPosTextH>(begin, adopted
), |
183 fRecord->replace<SkRecords::BoundedDrawPosTextH>(this->index(),
adopted), | 146 BoundedDrawPosTextH, |
184 SkRecords::BoundedDrawPosTextH, | 147 (&adopted, adjusted.fTop, adjusted.fBottom)); |
185 (&adopted, adjusted.fTop, adjusted.fBottom)); | 148 return true; |
186 } | 149 } |
187 }; | 150 }; |
188 | 151 void SkRecordBoundDrawPosTextH(SkRecord* record) { |
189 | 152 TextBounder pass; |
190 template <typename Pass> | 153 apply(&pass, record); |
191 static void run_pass(Pass& pass, SkRecord* record) { | |
192 for (; pass.index() < record->count(); pass.next()) { | |
193 record->mutate(pass.index(), pass); | |
194 } | |
195 } | 154 } |
196 | 155 |
197 } // namespace | 156 // Replaces PushCull with PairedPushCull, which lets us skip to the paired PopCu
ll when the canvas |
| 157 // can quickReject the cull rect. |
| 158 // There's no efficient way (yet?) to express this one as a pattern, so we write
a custom pass. |
| 159 class CullAnnotator { |
| 160 public: |
| 161 // Do nothing to most ops. |
| 162 template <typename T> void operator()(T*) {} |
198 | 163 |
| 164 void operator()(PushCull* push) { |
| 165 Pair pair = { fIndex, push }; |
| 166 fPushStack.push(pair); |
| 167 } |
199 | 168 |
200 void SkRecordNoopSaveRestores(SkRecord* record) { | 169 void operator()(PopCull* pop) { |
201 // Run SaveRestoreNooper until it doesn't make any more changes. | 170 Pair push = fPushStack.top(); |
202 bool changed; | 171 fPushStack.pop(); |
203 do { | 172 |
204 SaveRestoreNooper nooper(record); | 173 SkASSERT(fIndex > push.index); |
205 run_pass(nooper, record); | 174 unsigned skip = fIndex - push.index; |
206 changed = nooper.changed(); | 175 |
207 } while (changed); | 176 Adopted<PushCull> adopted(push.command); |
| 177 SkNEW_PLACEMENT_ARGS(fRecord->replace<PairedPushCull>(push.index, adopte
d), |
| 178 PairedPushCull, (&adopted, skip)); |
| 179 } |
| 180 |
| 181 void apply(SkRecord* record) { |
| 182 for (fRecord = record, fIndex = 0; fIndex < record->count(); fIndex++) { |
| 183 fRecord->mutate(fIndex, *this); |
| 184 } |
| 185 } |
| 186 |
| 187 private: |
| 188 struct Pair { |
| 189 unsigned index; |
| 190 PushCull* command; |
| 191 }; |
| 192 |
| 193 SkTDArray<Pair> fPushStack; |
| 194 SkRecord* fRecord; |
| 195 unsigned fIndex; |
| 196 }; |
| 197 void SkRecordAnnotateCullingPairs(SkRecord* record) { |
| 198 CullAnnotator pass; |
| 199 pass.apply(record); |
208 } | 200 } |
209 | |
210 void SkRecordAnnotateCullingPairs(SkRecord* record) { | |
211 CullAnnotator annotator(record); | |
212 run_pass(annotator, record); | |
213 } | |
214 | |
215 void SkRecordReduceDrawPosTextStrength(SkRecord* record) { | |
216 StrengthReducer reducer(record); | |
217 run_pass(reducer, record); | |
218 } | |
219 | |
220 void SkRecordBoundDrawPosTextH(SkRecord* record) { | |
221 TextBounder bounder(record); | |
222 run_pass(bounder, record); | |
223 } | |
OLD | NEW |