blob: aaa611cf392cf6126b5548f9c5f4e1bcb6a46e59 [file] [log] [blame]
commit-bot@chromium.orgc4b21e62014-04-11 18:33:31 +00001/*
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
commit-bot@chromium.orgad8ce572014-04-21 15:03:36 +00008#include "SkRecordOpts.h"
commit-bot@chromium.org506db0b2014-04-08 23:31:35 +00009
commit-bot@chromium.org73fffeb2014-05-05 21:59:52 +000010#include "SkRecordPattern.h"
commit-bot@chromium.org506db0b2014-04-08 23:31:35 +000011#include "SkRecords.h"
12#include "SkTDArray.h"
13
commit-bot@chromium.org73fffeb2014-05-05 21:59:52 +000014using namespace SkRecords;
15
commit-bot@chromium.orgad8ce572014-04-21 15:03:36 +000016void SkRecordOptimize(SkRecord* record) {
commit-bot@chromium.org88c3e272014-04-22 16:57:20 +000017 // TODO(mtklein): fuse independent optimizations to reduce number of passes?
18 SkRecordNoopSaveRestores(record);
commit-bot@chromium.orgad8ce572014-04-21 15:03:36 +000019 SkRecordAnnotateCullingPairs(record);
commit-bot@chromium.org88c3e272014-04-22 16:57:20 +000020 SkRecordReduceDrawPosTextStrength(record); // Helpful to run this before BoundDrawPosTextH.
21 SkRecordBoundDrawPosTextH(record);
commit-bot@chromium.orgad8ce572014-04-21 15:03:36 +000022}
23
commit-bot@chromium.org73fffeb2014-05-05 21:59:52 +000024// Most of the optimizations in this file are pattern-based. These are all defined 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.
commit-bot@chromium.org506db0b2014-04-08 23:31:35 +000028
commit-bot@chromium.org73fffeb2014-05-05 21:59:52 +000029// Run a pattern-based optimization once across the SkRecord, returning true if it made any changes.
30// It looks for spans which match Pass::Pattern, and when found calls onMatch() with the pattern,
31// record, and [begin,end) span of the commands that matched.
32template <typename Pass>
33static bool apply(Pass* pass, SkRecord* record) {
34 typename Pass::Pattern pattern;
35 bool changed = false;
36 unsigned begin, end = 0;
commit-bot@chromium.org506db0b2014-04-08 23:31:35 +000037
commit-bot@chromium.org73fffeb2014-05-05 21:59:52 +000038 while (pattern.search(record, &begin, &end)) {
39 changed |= pass->onMatch(record, &pattern, begin, end);
40 }
41 return changed;
42}
commit-bot@chromium.org506db0b2014-04-08 23:31:35 +000043
commit-bot@chromium.org88c3e272014-04-22 16:57:20 +000044// Turns logical no-op Save-[non-drawing command]*-Restore patterns into actual no-ops.
commit-bot@chromium.org73fffeb2014-05-05 21:59:52 +000045struct SaveRestoreNooper {
46 // Star matches greedily, so we also have to exclude Save and Restore.
47 typedef Pattern3<Is<Save>,
48 Star<Not<Or3<Is<Save>,
49 Is<Restore>,
50 IsDraw> > >,
51 Is<Restore> >
52 Pattern;
commit-bot@chromium.org88c3e272014-04-22 16:57:20 +000053
commit-bot@chromium.org73fffeb2014-05-05 21:59:52 +000054 bool onMatch(SkRecord* record, Pattern* pattern, unsigned begin, unsigned end) {
55 // If restore doesn't revert both matrix and clip, this isn't safe to noop away.
56 if (pattern->first<Save>()->flags != SkCanvas::kMatrixClip_SaveFlag) {
57 return false;
commit-bot@chromium.org2e0c32a2014-04-28 16:19:45 +000058 }
commit-bot@chromium.org73fffeb2014-05-05 21:59:52 +000059
60 // The entire span between Save and Restore (inclusively) does nothing.
61 for (unsigned i = begin; i < end; i++) {
62 record->replace<NoOp>(i);
63 }
64 return true;
commit-bot@chromium.org2e0c32a2014-04-28 16:19:45 +000065 }
commit-bot@chromium.org88c3e272014-04-22 16:57:20 +000066};
commit-bot@chromium.org73fffeb2014-05-05 21:59:52 +000067void SkRecordNoopSaveRestores(SkRecord* record) {
68 SaveRestoreNooper pass;
69 while (apply(&pass, record)); // Run until it stops changing things.
70}
commit-bot@chromium.org88c3e272014-04-22 16:57:20 +000071
commit-bot@chromium.org88c3e272014-04-22 16:57:20 +000072// Replaces DrawPosText with DrawPosTextH when all Y coordinates are equal.
commit-bot@chromium.org73fffeb2014-05-05 21:59:52 +000073struct StrengthReducer {
74 typedef Pattern1<Is<DrawPosText> > Pattern;
commit-bot@chromium.org88c3e272014-04-22 16:57:20 +000075
commit-bot@chromium.org73fffeb2014-05-05 21:59:52 +000076 bool onMatch(SkRecord* record, Pattern* pattern, unsigned begin, unsigned end) {
77 SkASSERT(end == begin + 1);
78 DrawPosText* draw = pattern->first<DrawPosText>();
commit-bot@chromium.org88c3e272014-04-22 16:57:20 +000079
commit-bot@chromium.org73fffeb2014-05-05 21:59:52 +000080 const unsigned points = draw->paint.countText(draw->text, draw->byteLength);
commit-bot@chromium.org2e0c32a2014-04-28 16:19:45 +000081 if (points == 0) {
commit-bot@chromium.org73fffeb2014-05-05 21:59:52 +000082 return false; // No point (ha!).
commit-bot@chromium.org88c3e272014-04-22 16:57:20 +000083 }
commit-bot@chromium.org2e0c32a2014-04-28 16:19:45 +000084
commit-bot@chromium.org73fffeb2014-05-05 21:59:52 +000085 const SkScalar firstY = draw->pos[0].fY;
commit-bot@chromium.org2e0c32a2014-04-28 16:19:45 +000086 for (unsigned i = 1; i < points; i++) {
commit-bot@chromium.org73fffeb2014-05-05 21:59:52 +000087 if (draw->pos[i].fY != firstY) {
88 return false; // Needs full power of DrawPosText.
commit-bot@chromium.org2e0c32a2014-04-28 16:19:45 +000089 }
90 }
91 // All ys are the same. We can replace DrawPosText with DrawPosTextH.
92
commit-bot@chromium.org73fffeb2014-05-05 21:59:52 +000093 // draw->pos is points SkPoints, [(x,y),(x,y),(x,y),(x,y), ... ].
commit-bot@chromium.org2e0c32a2014-04-28 16:19:45 +000094 // We're going to squint and look at that as 2*points SkScalars, [x,y,x,y,x,y,x,y, ...].
95 // Then we'll rearrange things so all the xs are in order up front, clobbering the ys.
96 SK_COMPILE_ASSERT(sizeof(SkPoint) == 2 * sizeof(SkScalar), SquintingIsNotSafe);
commit-bot@chromium.org73fffeb2014-05-05 21:59:52 +000097 SkScalar* scalars = &draw->pos[0].fX;
commit-bot@chromium.org2e0c32a2014-04-28 16:19:45 +000098 for (unsigned i = 0; i < 2*points; i += 2) {
99 scalars[i/2] = scalars[i];
100 }
101
commit-bot@chromium.org73fffeb2014-05-05 21:59:52 +0000102 // Extend lifetime of draw to the end of the loop so we can copy its paint.
103 Adopted<DrawPosText> adopted(draw);
104 SkNEW_PLACEMENT_ARGS(record->replace<DrawPosTextH>(begin, adopted),
105 DrawPosTextH,
106 (draw->text, draw->byteLength, scalars, firstY, draw->paint));
107 return true;
commit-bot@chromium.org88c3e272014-04-22 16:57:20 +0000108 }
commit-bot@chromium.org2e0c32a2014-04-28 16:19:45 +0000109};
commit-bot@chromium.org73fffeb2014-05-05 21:59:52 +0000110void SkRecordReduceDrawPosTextStrength(SkRecord* record) {
111 StrengthReducer pass;
112 apply(&pass, record);
113}
commit-bot@chromium.org88c3e272014-04-22 16:57:20 +0000114
115// Tries to replace DrawPosTextH with BoundedDrawPosTextH, which knows conservative upper and lower
116// bounds to use with SkCanvas::quickRejectY.
commit-bot@chromium.org73fffeb2014-05-05 21:59:52 +0000117struct TextBounder {
118 typedef Pattern1<Is<DrawPosTextH> > Pattern;
commit-bot@chromium.org88c3e272014-04-22 16:57:20 +0000119
commit-bot@chromium.org73fffeb2014-05-05 21:59:52 +0000120 bool onMatch(SkRecord* record, Pattern* pattern, unsigned begin, unsigned end) {
121 SkASSERT(end == begin + 1);
122 DrawPosTextH* draw = pattern->first<DrawPosTextH>();
commit-bot@chromium.org2e0c32a2014-04-28 16:19:45 +0000123
commit-bot@chromium.org2e0c32a2014-04-28 16:19:45 +0000124 // If we're drawing vertical text, none of the checks we're about to do make any sense.
125 // We'll need to call SkPaint::computeFastBounds() later, so bail if that's not possible.
commit-bot@chromium.org73fffeb2014-05-05 21:59:52 +0000126 if (draw->paint.isVerticalText() || !draw->paint.canComputeFastBounds()) {
127 return false;
commit-bot@chromium.org2e0c32a2014-04-28 16:19:45 +0000128 }
129
130 // Rather than checking the top and bottom font metrics, we guess. Actually looking up the
131 // top and bottom metrics is slow, and this overapproximation should be good enough.
commit-bot@chromium.org73fffeb2014-05-05 21:59:52 +0000132 const SkScalar buffer = draw->paint.getTextSize() * 1.5f;
commit-bot@chromium.org2e0c32a2014-04-28 16:19:45 +0000133 SkDEBUGCODE(SkPaint::FontMetrics metrics;)
commit-bot@chromium.org73fffeb2014-05-05 21:59:52 +0000134 SkDEBUGCODE(draw->paint.getFontMetrics(&metrics);)
commit-bot@chromium.org2e0c32a2014-04-28 16:19:45 +0000135 SkASSERT(-buffer <= metrics.fTop);
136 SkASSERT(+buffer >= metrics.fBottom);
137
138 // Let the paint adjust the text bounds. We don't care about left and right here, so we use
139 // 0 and 1 respectively just so the bounds rectangle isn't empty.
140 SkRect bounds;
commit-bot@chromium.org73fffeb2014-05-05 21:59:52 +0000141 bounds.set(0, draw->y - buffer, SK_Scalar1, draw->y + buffer);
142 SkRect adjusted = draw->paint.computeFastBounds(bounds, &bounds);
commit-bot@chromium.org2e0c32a2014-04-28 16:19:45 +0000143
commit-bot@chromium.org73fffeb2014-05-05 21:59:52 +0000144 Adopted<DrawPosTextH> adopted(draw);
145 SkNEW_PLACEMENT_ARGS(record->replace<BoundedDrawPosTextH>(begin, adopted),
146 BoundedDrawPosTextH,
147 (&adopted, adjusted.fTop, adjusted.fBottom));
148 return true;
commit-bot@chromium.org2e0c32a2014-04-28 16:19:45 +0000149 }
commit-bot@chromium.org88c3e272014-04-22 16:57:20 +0000150};
commit-bot@chromium.org88c3e272014-04-22 16:57:20 +0000151void SkRecordBoundDrawPosTextH(SkRecord* record) {
commit-bot@chromium.org73fffeb2014-05-05 21:59:52 +0000152 TextBounder pass;
153 apply(&pass, record);
154}
155
156// Replaces PushCull with PairedPushCull, which lets us skip to the paired PopCull 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.
159class CullAnnotator {
160public:
161 // Do nothing to most ops.
162 template <typename T> void operator()(T*) {}
163
164 void operator()(PushCull* push) {
165 Pair pair = { fIndex, push };
166 fPushStack.push(pair);
167 }
168
169 void operator()(PopCull* pop) {
170 Pair push = fPushStack.top();
171 fPushStack.pop();
172
173 SkASSERT(fIndex > push.index);
174 unsigned skip = fIndex - push.index;
175
176 Adopted<PushCull> adopted(push.command);
177 SkNEW_PLACEMENT_ARGS(fRecord->replace<PairedPushCull>(push.index, adopted),
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
187private:
188 struct Pair {
189 unsigned index;
190 PushCull* command;
191 };
192
193 SkTDArray<Pair> fPushStack;
194 SkRecord* fRecord;
195 unsigned fIndex;
196};
197void SkRecordAnnotateCullingPairs(SkRecord* record) {
198 CullAnnotator pass;
199 pass.apply(record);
commit-bot@chromium.org88c3e272014-04-22 16:57:20 +0000200}