1/*
2 * Copyright 2013 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 "gm.h"
9#include "SkDebugCanvas.h"
10#include "SkPictureFlat.h"
11#include "SkPictureRecorder.h"
12
13#define WARN(msg)                                           \
14    SkDebugf("%s:%d: %s\n", __FILE__, __LINE__, msg);
15
16// Do the commands in 'input' match the supplied pattern? Note: this is a pretty
17// heavy-weight operation since we are drawing the picture into a debug canvas
18// to extract the commands.
19static bool check_pattern(SkPicture& input, const SkTDArray<DrawType> &pattern) {
20    SkDebugCanvas debugCanvas(input.width(), input.height());
21    debugCanvas.setBounds(input.width(), input.height());
22    input.draw(&debugCanvas);
23
24    if (pattern.count() != debugCanvas.getSize()) {
25        return false;
26    }
27
28    for (int i = 0; i < pattern.count(); ++i) {
29        if (pattern[i] != debugCanvas.getDrawCommandAt(i)->getType()) {
30            return false;
31        }
32    }
33
34    return true;
35}
36
37// construct the pattern removed by the SkPictureRecord::remove_save_layer1
38// optimization, i.e.:
39//   SAVE_LAYER
40//       DRAW_BITMAP|DRAW_BITMAP_MATRIX|DRAW_BITMAP_NINE|DRAW_BITMAP_RECT_TO_RECT
41//   RESTORE
42//
43// saveLayerHasPaint - control if the saveLayer has a paint (the optimization
44//                     takes a different path if this is false)
45// dbmr2rHasPaint    - control if the dbmr2r has a paint (the optimization
46//                     takes a different path if this is false)
47// colorsMatch       - control if the saveLayer and dbmr2r paint colors
48//                     match (the optimization will fail if they do not)
49static SkPicture* create_save_layer_opt_1(SkTDArray<DrawType>* preOptPattern,
50                                          SkTDArray<DrawType>* postOptPattern,
51                                          const SkBitmap& checkerBoard,
52                                          bool saveLayerHasPaint,
53                                          bool dbmr2rHasPaint,
54                                          bool colorsMatch)  {
55    // Create the pattern that should trigger the optimization
56    preOptPattern->setCount(5);
57    (*preOptPattern)[0] = SAVE;
58    (*preOptPattern)[1] = SAVE_LAYER;
59    (*preOptPattern)[2] = DRAW_BITMAP_RECT_TO_RECT;
60    (*preOptPattern)[3] = RESTORE;
61    (*preOptPattern)[4] = RESTORE;
62
63    if (colorsMatch) {
64        // Create the pattern that should appear after the optimization
65        postOptPattern->setCount(5);
66        (*postOptPattern)[0] = SAVE; // extra save/restore added by extra draw
67        (*postOptPattern)[1] = SAVE;
68        (*postOptPattern)[2] = DRAW_BITMAP_RECT_TO_RECT;
69        (*postOptPattern)[3] = RESTORE;
70        (*postOptPattern)[4] = RESTORE;
71    } else {
72        // Create the pattern that appears if the optimization doesn't fire
73        postOptPattern->setCount(7);
74        (*postOptPattern)[0] = SAVE; // extra save/restore added by extra draw
75        (*postOptPattern)[1] = SAVE;
76        (*postOptPattern)[2] = SAVE_LAYER;
77        (*postOptPattern)[3] = DRAW_BITMAP_RECT_TO_RECT;
78        (*postOptPattern)[4] = RESTORE;
79        (*postOptPattern)[5] = RESTORE;
80        (*postOptPattern)[6] = RESTORE;
81    }
82
83    SkPictureRecorder recorder;
84
85    SkCanvas* canvas = recorder.beginRecording(100, 100, NULL, 0);
86    // have to disable the optimizations while generating the picture
87    recorder.internalOnly_EnableOpts(false);
88
89    SkPaint saveLayerPaint;
90    saveLayerPaint.setColor(0xCC000000);
91
92    // saveLayer's 'bounds' parameter must be NULL for this optimization
93    if (saveLayerHasPaint) {
94        canvas->saveLayer(NULL, &saveLayerPaint);
95    } else {
96        canvas->saveLayer(NULL, NULL);
97    }
98
99    SkRect rect = { 10, 10, 90, 90 };
100
101    // The dbmr2r's paint must be opaque
102    SkPaint dbmr2rPaint;
103    if (colorsMatch) {
104        dbmr2rPaint.setColor(0xFF000000);
105    } else {
106        dbmr2rPaint.setColor(0xFFFF0000);
107    }
108
109    if (dbmr2rHasPaint) {
110        canvas->drawBitmapRectToRect(checkerBoard, NULL, rect, &dbmr2rPaint);
111    } else {
112        canvas->drawBitmapRectToRect(checkerBoard, NULL, rect, NULL);
113    }
114    canvas->restore();
115
116    return recorder.endRecording();
117}
118
119// straight-ahead version that is seen in the skps
120static SkPicture* create_save_layer_opt_1_v1(SkTDArray<DrawType>* preOptPattern,
121                                             SkTDArray<DrawType>* postOptPattern,
122                                             const SkBitmap& checkerBoard) {
123    return create_save_layer_opt_1(preOptPattern, postOptPattern, checkerBoard,
124                                   true,   // saveLayer has a paint
125                                   true,   // dbmr2r has a paint
126                                   true);  // and the colors match
127}
128
129// alternate version that should still succeed
130static SkPicture* create_save_layer_opt_1_v2(SkTDArray<DrawType>* preOptPattern,
131                                             SkTDArray<DrawType>* postOptPattern,
132                                             const SkBitmap& checkerBoard) {
133    return create_save_layer_opt_1(preOptPattern, postOptPattern, checkerBoard,
134                                   false,  // saveLayer doesn't have a paint!
135                                   true,   // dbmr2r has a paint
136                                   true);  // color matching not really applicable
137}
138
139// alternate version that should still succeed
140static SkPicture* create_save_layer_opt_1_v3(SkTDArray<DrawType>* preOptPattern,
141                                             SkTDArray<DrawType>* postOptPattern,
142                                             const SkBitmap& checkerBoard) {
143    return create_save_layer_opt_1(preOptPattern, postOptPattern, checkerBoard,
144                                   true,   // saveLayer has a paint
145                                   false,  // dbmr2r doesn't have a paint!
146                                   true);  // color matching not really applicable
147}
148
149// version in which the optimization fails b.c. the colors don't match
150static SkPicture* create_save_layer_opt_1_v4(SkTDArray<DrawType>* preOptPattern,
151                                             SkTDArray<DrawType>* postOptPattern,
152                                             const SkBitmap& checkerBoard) {
153    return create_save_layer_opt_1(preOptPattern, postOptPattern, checkerBoard,
154                                   true,   // saveLayer has a paint
155                                   true,   // dbmr2r has a paint
156                                   false); // and the colors don't match!
157}
158
159// construct the pattern removed by the SkPictureRecord::remove_save_layer2
160// optimization, i.e.:
161//   SAVE_LAYER (with NULL == bounds)
162//      SAVE
163//         CLIP_RECT
164//         DRAW_BITMAP|DRAW_BITMAP_MATRIX|DRAW_BITMAP_NINE|DRAW_BITMAP_RECT_TO_RECT
165//      RESTORE
166//   RESTORE
167//
168// saveLayerHasPaint - control if the saveLayer has a paint (the optimization
169//                     takes a different path if this is false)
170// dbmr2rHasPaint    - control if the dbmr2r has a paint (the optimization
171//                     takes a different path if this is false)
172// colorsMatch       - control if the saveLayer and dbmr2r paint colors
173//                     match (the optimization will fail if they do not)
174static SkPicture* create_save_layer_opt_2(SkTDArray<DrawType>* preOptPattern,
175                                          SkTDArray<DrawType>* postOptPattern,
176                                          const SkBitmap& checkerBoard,
177                                          bool saveLayerHasPaint,
178                                          bool dbmr2rHasPaint,
179                                          bool colorsMatch)  {
180    // Create the pattern that should trigger the optimization
181    preOptPattern->setCount(8);
182    (*preOptPattern)[0] = SAVE;
183    (*preOptPattern)[1] = SAVE_LAYER;
184    (*preOptPattern)[2] = SAVE;
185    (*preOptPattern)[3] = CLIP_RECT;
186    (*preOptPattern)[4] = DRAW_BITMAP_RECT_TO_RECT;
187    (*preOptPattern)[5] = RESTORE;
188    (*preOptPattern)[6] = RESTORE;
189    (*preOptPattern)[7] = RESTORE;
190
191    if (colorsMatch) {
192        // Create the pattern that should appear after the optimization
193        postOptPattern->setCount(8);
194        (*postOptPattern)[0] = SAVE; // extra save/restore added by extra draw
195        (*postOptPattern)[1] = SAVE;
196        (*postOptPattern)[2] = SAVE;
197        (*postOptPattern)[3] = CLIP_RECT;
198        (*postOptPattern)[4] = DRAW_BITMAP_RECT_TO_RECT;
199        (*postOptPattern)[5] = RESTORE;
200        (*postOptPattern)[6] = RESTORE;
201        (*postOptPattern)[7] = RESTORE;
202    } else {
203        // Create the pattern that appears if the optimization doesn't fire
204        postOptPattern->setCount(10);
205        (*postOptPattern)[0] = SAVE; // extra save/restore added by extra draw
206        (*postOptPattern)[1] = SAVE;
207        (*postOptPattern)[2] = SAVE_LAYER;
208        (*postOptPattern)[3] = SAVE;
209        (*postOptPattern)[4] = CLIP_RECT;
210        (*postOptPattern)[5] = DRAW_BITMAP_RECT_TO_RECT;
211        (*postOptPattern)[6] = RESTORE;
212        (*postOptPattern)[7] = RESTORE;
213        (*postOptPattern)[8] = RESTORE;
214        (*postOptPattern)[9] = RESTORE;
215    }
216
217    SkPictureRecorder recorder;
218
219    SkCanvas* canvas = recorder.beginRecording(100, 100, NULL, 0);
220    // have to disable the optimizations while generating the picture
221    recorder.internalOnly_EnableOpts(false);
222
223    SkPaint saveLayerPaint;
224    saveLayerPaint.setColor(0xCC000000);
225
226    // saveLayer's 'bounds' parameter must be NULL for this optimization
227    if (saveLayerHasPaint) {
228        canvas->saveLayer(NULL, &saveLayerPaint);
229    } else {
230        canvas->saveLayer(NULL, NULL);
231    }
232
233    canvas->save();
234
235    SkRect rect = { 10, 10, 90, 90 };
236    canvas->clipRect(rect);
237
238    // The dbmr2r's paint must be opaque
239    SkPaint dbmr2rPaint;
240    if (colorsMatch) {
241        dbmr2rPaint.setColor(0xFF000000);
242    } else {
243        dbmr2rPaint.setColor(0xFFFF0000);
244    }
245
246    if (dbmr2rHasPaint) {
247        canvas->drawBitmapRectToRect(checkerBoard, NULL, rect, &dbmr2rPaint);
248    } else {
249        canvas->drawBitmapRectToRect(checkerBoard, NULL, rect, NULL);
250    }
251    canvas->restore();
252    canvas->restore();
253
254    return recorder.endRecording();
255}
256
257// straight-ahead version that is seen in the skps
258static SkPicture* create_save_layer_opt_2_v1(SkTDArray<DrawType>* preOptPattern,
259                                             SkTDArray<DrawType>* postOptPattern,
260                                             const SkBitmap& checkerBoard) {
261    return create_save_layer_opt_2(preOptPattern, postOptPattern, checkerBoard,
262                                   true,   // saveLayer has a paint
263                                   true,   // dbmr2r has a paint
264                                   true);  // and the colors match
265}
266
267// alternate version that should still succeed
268static SkPicture* create_save_layer_opt_2_v2(SkTDArray<DrawType>* preOptPattern,
269                                             SkTDArray<DrawType>* postOptPattern,
270                                             const SkBitmap& checkerBoard) {
271    return create_save_layer_opt_2(preOptPattern, postOptPattern, checkerBoard,
272                                   false,  // saveLayer doesn't have a paint!
273                                   true,   // dbmr2r has a paint
274                                   true);  // color matching not really applicable
275}
276
277// alternate version that should still succeed
278static SkPicture* create_save_layer_opt_2_v3(SkTDArray<DrawType>* preOptPattern,
279                                             SkTDArray<DrawType>* postOptPattern,
280                                             const SkBitmap& checkerBoard) {
281    return create_save_layer_opt_2(preOptPattern, postOptPattern, checkerBoard,
282                                   true,   // saveLayer has a paint
283                                   false,  // dbmr2r doesn't have a paint!
284                                   true);  // color matching not really applicable
285}
286
287// version in which the optimization fails b.c. the colors don't match
288static SkPicture* create_save_layer_opt_2_v4(SkTDArray<DrawType>* preOptPattern,
289                                             SkTDArray<DrawType>* postOptPattern,
290                                             const SkBitmap& checkerBoard) {
291    return create_save_layer_opt_2(preOptPattern, postOptPattern, checkerBoard,
292                                   true,   // saveLayer has a paint
293                                   true,   // dbmr2r has a paint
294                                   false); // and the colors don't match!
295}
296
297// As our .skp optimizations get folded into the captured skps our code will
298// no longer be locally exercised. This GM manually constructs the patterns
299// our optimizations will remove to test them. It acts as both a GM and a unit
300// test
301class OptimizationsGM : public skiagm::GM {
302public:
303    OptimizationsGM() {
304        this->makeCheckerboard();
305    }
306
307    static const int kWidth = 800;
308    static const int kHeight = 800;
309
310protected:
311    uint32_t onGetFlags() const SK_OVERRIDE {
312        // One optimization changes the color drawn slightly in a 565 target.
313        // We've decided it's innocuous, so we disable this GM when targeting 565.
314        // Revisit this if we get finer-grained control: it'd be nice to keep drawing directly.
315        // For more, see skia:1994.
316        return skiagm::GM::kSkip565_Flag;
317    }
318
319    SkString onShortName() {
320        return SkString("optimizations");
321    }
322
323    SkISize onISize() { return SkISize::Make(kWidth, kHeight); }
324
325    typedef SkPicture* (*PFCreateOpt)(SkTDArray<DrawType> *preOptPattern,
326                                      SkTDArray<DrawType> *postOptPattern,
327                                      const SkBitmap& checkerBoard);
328
329    virtual void onDraw(SkCanvas* canvas) {
330
331        PFCreateOpt gOpts[] = {
332            create_save_layer_opt_1_v1,
333            create_save_layer_opt_1_v2,
334            create_save_layer_opt_1_v3,
335            create_save_layer_opt_1_v4,
336            create_save_layer_opt_2_v1,
337            create_save_layer_opt_2_v2,
338            create_save_layer_opt_2_v3,
339            create_save_layer_opt_2_v4,
340        };
341
342        SkTDArray<DrawType> prePattern, postPattern;
343        int xPos = 0, yPos = 0;
344
345        for (size_t i = 0; i < SK_ARRAY_COUNT(gOpts); ++i) {
346            SkAutoTUnref<SkPicture> pre((*gOpts[i])(&prePattern, &postPattern, fCheckerboard));
347
348            if (!(check_pattern(*pre, prePattern))) {
349                WARN("Pre optimization pattern mismatch");
350                SkASSERT(0);
351            }
352
353            canvas->save();
354                canvas->translate(SkIntToScalar(xPos), SkIntToScalar(yPos));
355                pre->draw(canvas);
356                xPos += pre->width();
357            canvas->restore();
358
359            // re-render the 'pre' picture and thus 'apply' the optimization
360            SkPictureRecorder recorder;
361
362            SkCanvas* recordCanvas = recorder.beginRecording(pre->width(), pre->height(), NULL, 0);
363
364            pre->draw(recordCanvas);
365
366            SkAutoTUnref<SkPicture> post(recorder.endRecording());
367
368            if (!(check_pattern(*post, postPattern))) {
369                WARN("Post optimization pattern mismatch");
370                SkASSERT(0);
371            }
372
373            canvas->save();
374                canvas->translate(SkIntToScalar(xPos), SkIntToScalar(yPos));
375                post->draw(canvas);
376                xPos += post->width();
377            canvas->restore();
378
379            if (xPos >= kWidth) {
380                // start a new line
381                xPos = 0;
382                yPos += post->height();
383            }
384
385            // TODO: we could also render the pre and post pictures to bitmaps
386            // and manually compare them in this method
387        }
388    }
389
390private:
391    void makeCheckerboard() {
392        static const unsigned int kCheckerboardWidth = 16;
393        static const unsigned int kCheckerboardHeight = 16;
394
395        fCheckerboard.allocN32Pixels(kCheckerboardWidth, kCheckerboardHeight);
396        for (unsigned int y = 0; y < kCheckerboardHeight; y += 2) {
397            SkPMColor* scanline = fCheckerboard.getAddr32(0, y);
398            for (unsigned int x = 0; x < kCheckerboardWidth; x += 2) {
399                *scanline++ = 0xFFFFFFFF;
400                *scanline++ = 0xFF000000;
401            }
402            scanline = fCheckerboard.getAddr32(0, y + 1);
403            for (unsigned int x = 0; x < kCheckerboardWidth; x += 2) {
404                *scanline++ = 0xFF000000;
405                *scanline++ = 0xFFFFFFFF;
406            }
407        }
408    }
409
410    SkBitmap fCheckerboard;
411
412    typedef skiagm::GM INHERITED;
413};
414
415//////////////////////////////////////////////////////////////////////////////
416
417DEF_GM( return new OptimizationsGM; )
418