Home | History | Annotate | Download | only in core
      1 /*
      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 
      8 #include "SkRecordOpts.h"
      9 
     10 #include "SkRecordPattern.h"
     11 #include "SkRecords.h"
     12 #include "SkTDArray.h"
     13 
     14 using namespace SkRecords;
     15 
     16 void SkRecordOptimize(SkRecord* record) {
     17     // TODO(mtklein): fuse independent optimizations to reduce number of passes?
     18     SkRecordNoopCulls(record);
     19     SkRecordNoopSaveRestores(record);
     20     // TODO(mtklein): figure out why we draw differently and reenable
     21     //SkRecordNoopSaveLayerDrawRestores(record);
     22 
     23     SkRecordAnnotateCullingPairs(record);
     24     SkRecordReduceDrawPosTextStrength(record);  // Helpful to run this before BoundDrawPosTextH.
     25     SkRecordBoundDrawPosTextH(record);
     26 }
     27 
     28 // Most of the optimizations in this file are pattern-based.  These are all defined as structs with:
     29 //   - a Pattern typedef
     30 //   - a bool onMatch(SkRceord*, Pattern*, unsigned begin, unsigned end) method,
     31 //     which returns true if it made changes and false if not.
     32 
     33 // Run a pattern-based optimization once across the SkRecord, returning true if it made any changes.
     34 // It looks for spans which match Pass::Pattern, and when found calls onMatch() with the pattern,
     35 // record, and [begin,end) span of the commands that matched.
     36 template <typename Pass>
     37 static bool apply(Pass* pass, SkRecord* record) {
     38     typename Pass::Pattern pattern;
     39     bool changed = false;
     40     unsigned begin, end = 0;
     41 
     42     while (pattern.search(record, &begin, &end)) {
     43         changed |= pass->onMatch(record, &pattern, begin, end);
     44     }
     45     return changed;
     46 }
     47 
     48 struct CullNooper {
     49     typedef Pattern3<Is<PushCull>, Star<Is<NoOp> >, Is<PopCull> > Pattern;
     50 
     51     bool onMatch(SkRecord* record, Pattern* pattern, unsigned begin, unsigned end) {
     52         record->replace<NoOp>(begin);  // PushCull
     53         record->replace<NoOp>(end-1);  // PopCull
     54         return true;
     55     }
     56 };
     57 
     58 void SkRecordNoopCulls(SkRecord* record) {
     59     CullNooper pass;
     60     while (apply(&pass, record));
     61 }
     62 
     63 // Turns the logical NoOp Save and Restore in Save-Draw*-Restore patterns into actual NoOps.
     64 struct SaveOnlyDrawsRestoreNooper {
     65     typedef Pattern3<Is<Save>,
     66                      Star<Or<Is<NoOp>, IsDraw> >,
     67                      Is<Restore> >
     68         Pattern;
     69 
     70     bool onMatch(SkRecord* record, Pattern* pattern, unsigned begin, unsigned end) {
     71         record->replace<NoOp>(begin);  // Save
     72         record->replace<NoOp>(end-1);  // Restore
     73         return true;
     74     }
     75 };
     76 // Turns logical no-op Save-[non-drawing command]*-Restore patterns into actual no-ops.
     77 struct SaveNoDrawsRestoreNooper {
     78     // Star matches greedily, so we also have to exclude Save and Restore.
     79     typedef Pattern3<Is<Save>,
     80                      Star<Not<Or3<Is<Save>,
     81                                   Is<Restore>,
     82                                   IsDraw> > >,
     83                      Is<Restore> >
     84         Pattern;
     85 
     86     bool onMatch(SkRecord* record, Pattern* pattern, unsigned begin, unsigned end) {
     87         // If restore doesn't revert both matrix and clip, this isn't safe to noop away.
     88         if (pattern->first<Save>()->flags != SkCanvas::kMatrixClip_SaveFlag) {
     89             return false;
     90         }
     91 
     92         // The entire span between Save and Restore (inclusively) does nothing.
     93         for (unsigned i = begin; i < end; i++) {
     94             record->replace<NoOp>(i);
     95         }
     96         return true;
     97     }
     98 };
     99 void SkRecordNoopSaveRestores(SkRecord* record) {
    100     SaveOnlyDrawsRestoreNooper onlyDraws;
    101     SaveNoDrawsRestoreNooper noDraws;
    102 
    103     // Run until they stop changing things.
    104     while (apply(&onlyDraws, record) || apply(&noDraws, record));
    105 }
    106 
    107 // For some SaveLayer-[drawing command]-Restore patterns, merge the SaveLayer's alpha into the
    108 // draw, and no-op the SaveLayer and Restore.
    109 struct SaveLayerDrawRestoreNooper {
    110     typedef Pattern3<Is<SaveLayer>, IsDraw, Is<Restore> > Pattern;
    111 
    112     bool onMatch(SkRecord* record, Pattern* pattern, unsigned begin, unsigned end) {
    113         SaveLayer* saveLayer = pattern->first<SaveLayer>();
    114         if (saveLayer->bounds != NULL) {
    115             // SaveLayer with bounds is too tricky for us.
    116             return false;
    117         }
    118 
    119         SkPaint* layerPaint = saveLayer->paint;
    120         if (NULL == layerPaint) {
    121             // There wasn't really any point to this SaveLayer at all.
    122             return KillSaveLayerAndRestore(record, begin);
    123         }
    124 
    125         SkPaint* drawPaint = pattern->second<SkPaint>();
    126         if (drawPaint == NULL) {
    127             // We can just give the draw the SaveLayer's paint.
    128             // TODO(mtklein): figure out how to do this clearly
    129             return false;
    130         }
    131 
    132         const uint32_t layerColor = layerPaint->getColor();
    133         const uint32_t  drawColor =  drawPaint->getColor();
    134         if (!IsOnlyAlpha(layerColor)  || !IsOpaque(drawColor) ||
    135             HasAnyEffect(*layerPaint) || HasAnyEffect(*drawPaint)) {
    136             // Too fancy for us.  Actually, as long as layerColor is just an alpha
    137             // we can blend it into drawColor's alpha; drawColor doesn't strictly have to be opaque.
    138             return false;
    139         }
    140 
    141         drawPaint->setColor(SkColorSetA(drawColor, SkColorGetA(layerColor)));
    142         return KillSaveLayerAndRestore(record, begin);
    143     }
    144 
    145     static bool KillSaveLayerAndRestore(SkRecord* record, unsigned saveLayerIndex) {
    146         record->replace<NoOp>(saveLayerIndex);    // SaveLayer
    147         record->replace<NoOp>(saveLayerIndex+2);  // Restore
    148         return true;
    149     }
    150 
    151     static bool HasAnyEffect(const SkPaint& paint) {
    152         return paint.getPathEffect()  ||
    153                paint.getShader()      ||
    154                paint.getXfermode()    ||
    155                paint.getMaskFilter()  ||
    156                paint.getColorFilter() ||
    157                paint.getRasterizer()  ||
    158                paint.getLooper()      ||
    159                paint.getImageFilter();
    160     }
    161 
    162     static bool IsOpaque(SkColor color) {
    163         return SkColorGetA(color) == SK_AlphaOPAQUE;
    164     }
    165     static bool IsOnlyAlpha(SkColor color) {
    166         return SK_ColorTRANSPARENT == SkColorSetA(color, SK_AlphaTRANSPARENT);
    167     }
    168 };
    169 void SkRecordNoopSaveLayerDrawRestores(SkRecord* record) {
    170     SaveLayerDrawRestoreNooper pass;
    171     apply(&pass, record);
    172 }
    173 
    174 
    175 // Replaces DrawPosText with DrawPosTextH when all Y coordinates are equal.
    176 struct StrengthReducer {
    177     typedef Pattern1<Is<DrawPosText> > Pattern;
    178 
    179     bool onMatch(SkRecord* record, Pattern* pattern, unsigned begin, unsigned end) {
    180         SkASSERT(end == begin + 1);
    181         DrawPosText* draw = pattern->first<DrawPosText>();
    182 
    183         const unsigned points = draw->paint.countText(draw->text, draw->byteLength);
    184         if (points == 0) {
    185             return false;  // No point (ha!).
    186         }
    187 
    188         const SkScalar firstY = draw->pos[0].fY;
    189         for (unsigned i = 1; i < points; i++) {
    190             if (draw->pos[i].fY != firstY) {
    191                 return false;  // Needs full power of DrawPosText.
    192             }
    193         }
    194         // All ys are the same.  We can replace DrawPosText with DrawPosTextH.
    195 
    196         // draw->pos is points SkPoints, [(x,y),(x,y),(x,y),(x,y), ... ].
    197         // We're going to squint and look at that as 2*points SkScalars, [x,y,x,y,x,y,x,y, ...].
    198         // Then we'll rearrange things so all the xs are in order up front, clobbering the ys.
    199         SK_COMPILE_ASSERT(sizeof(SkPoint) == 2 * sizeof(SkScalar), SquintingIsNotSafe);
    200         SkScalar* scalars = &draw->pos[0].fX;
    201         for (unsigned i = 0; i < 2*points; i += 2) {
    202             scalars[i/2] = scalars[i];
    203         }
    204 
    205         // Extend lifetime of draw to the end of the loop so we can copy its paint.
    206         Adopted<DrawPosText> adopted(draw);
    207         SkNEW_PLACEMENT_ARGS(record->replace<DrawPosTextH>(begin, adopted),
    208                              DrawPosTextH,
    209                              (draw->paint, draw->text, draw->byteLength, scalars, firstY));
    210         return true;
    211     }
    212 };
    213 void SkRecordReduceDrawPosTextStrength(SkRecord* record) {
    214     StrengthReducer pass;
    215     apply(&pass, record);
    216 }
    217 
    218 // Tries to replace DrawPosTextH with BoundedDrawPosTextH, which knows conservative upper and lower
    219 // bounds to use with SkCanvas::quickRejectY.
    220 struct TextBounder {
    221     typedef Pattern1<Is<DrawPosTextH> > Pattern;
    222 
    223     bool onMatch(SkRecord* record, Pattern* pattern, unsigned begin, unsigned end) {
    224         SkASSERT(end == begin + 1);
    225         DrawPosTextH* draw = pattern->first<DrawPosTextH>();
    226 
    227         // If we're drawing vertical text, none of the checks we're about to do make any sense.
    228         // We'll need to call SkPaint::computeFastBounds() later, so bail if that's not possible.
    229         if (draw->paint.isVerticalText() || !draw->paint.canComputeFastBounds()) {
    230             return false;
    231         }
    232 
    233         // Rather than checking the top and bottom font metrics, we guess.  Actually looking up the
    234         // top and bottom metrics is slow, and this overapproximation should be good enough.
    235         const SkScalar buffer = draw->paint.getTextSize() * 1.5f;
    236         SkDEBUGCODE(SkPaint::FontMetrics metrics;)
    237         SkDEBUGCODE(draw->paint.getFontMetrics(&metrics);)
    238         SkASSERT(-buffer <= metrics.fTop);
    239         SkASSERT(+buffer >= metrics.fBottom);
    240 
    241         // Let the paint adjust the text bounds.  We don't care about left and right here, so we use
    242         // 0 and 1 respectively just so the bounds rectangle isn't empty.
    243         SkRect bounds;
    244         bounds.set(0, draw->y - buffer, SK_Scalar1, draw->y + buffer);
    245         SkRect adjusted = draw->paint.computeFastBounds(bounds, &bounds);
    246 
    247         Adopted<DrawPosTextH> adopted(draw);
    248         SkNEW_PLACEMENT_ARGS(record->replace<BoundedDrawPosTextH>(begin, adopted),
    249                              BoundedDrawPosTextH,
    250                              (&adopted, adjusted.fTop, adjusted.fBottom));
    251         return true;
    252     }
    253 };
    254 void SkRecordBoundDrawPosTextH(SkRecord* record) {
    255     TextBounder pass;
    256     apply(&pass, record);
    257 }
    258 
    259 // Replaces PushCull with PairedPushCull, which lets us skip to the paired PopCull when the canvas
    260 // can quickReject the cull rect.
    261 // There's no efficient way (yet?) to express this one as a pattern, so we write a custom pass.
    262 class CullAnnotator {
    263 public:
    264     // Do nothing to most ops.
    265     template <typename T> void operator()(T*) {}
    266 
    267     void operator()(PushCull* push) {
    268         Pair pair = { fIndex, push };
    269         fPushStack.push(pair);
    270     }
    271 
    272     void operator()(PopCull* pop) {
    273         Pair push = fPushStack.top();
    274         fPushStack.pop();
    275 
    276         SkASSERT(fIndex > push.index);
    277         unsigned skip = fIndex - push.index;
    278 
    279         Adopted<PushCull> adopted(push.command);
    280         SkNEW_PLACEMENT_ARGS(fRecord->replace<PairedPushCull>(push.index, adopted),
    281                              PairedPushCull, (&adopted, skip));
    282     }
    283 
    284     void apply(SkRecord* record) {
    285         for (fRecord = record, fIndex = 0; fIndex < record->count(); fIndex++) {
    286             fRecord->mutate<void>(fIndex, *this);
    287         }
    288     }
    289 
    290 private:
    291     struct Pair {
    292         unsigned index;
    293         PushCull* command;
    294     };
    295 
    296     SkTDArray<Pair> fPushStack;
    297     SkRecord* fRecord;
    298     unsigned fIndex;
    299 };
    300 void SkRecordAnnotateCullingPairs(SkRecord* record) {
    301     CullAnnotator pass;
    302     pass.apply(record);
    303 }
    304