RecordingCanvasTests.cpp revision 6e49c9f007c879f05b035c40c0ba543c00f9d0d0
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <gtest/gtest.h>
18
19#include <DeferredLayerUpdater.h>
20#include <RecordedOp.h>
21#include <RecordingCanvas.h>
22#include <hwui/Paint.h>
23#include <minikin/Layout.h>
24#include <tests/common/TestUtils.h>
25#include <utils/Color.h>
26
27#include <SkGradientShader.h>
28#include <SkShader.h>
29
30namespace android {
31namespace uirenderer {
32
33static void playbackOps(const DisplayList& displayList,
34        std::function<void(const RecordedOp&)> opReceiver) {
35    for (auto& chunk : displayList.getChunks()) {
36        for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
37            RecordedOp* op = displayList.getOps()[opIndex];
38            opReceiver(*op);
39        }
40    }
41}
42
43static void validateSingleOp(std::unique_ptr<DisplayList>& dl,
44        std::function<void(const RecordedOp& op)> opValidator) {
45    ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
46    opValidator(*(dl->getOps()[0]));
47}
48
49TEST(RecordingCanvas, emptyPlayback) {
50    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
51        canvas.save(SaveFlags::MatrixClip);
52        canvas.restore();
53    });
54    playbackOps(*dl, [](const RecordedOp& op) { ADD_FAILURE(); });
55}
56
57TEST(RecordingCanvas, clipRect) {
58    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) {
59        canvas.save(SaveFlags::MatrixClip);
60        canvas.clipRect(0, 0, 100, 100, kIntersect_SkClipOp);
61        canvas.drawRect(0, 0, 50, 50, SkPaint());
62        canvas.drawRect(50, 50, 100, 100, SkPaint());
63        canvas.restore();
64    });
65
66    ASSERT_EQ(2u, dl->getOps().size()) << "Must be exactly two ops";
67    EXPECT_CLIP_RECT(Rect(100, 100), dl->getOps()[0]->localClip);
68    EXPECT_CLIP_RECT(Rect(100, 100), dl->getOps()[1]->localClip);
69    EXPECT_EQ(dl->getOps()[0]->localClip, dl->getOps()[1]->localClip)
70            << "Clip should be serialized once";
71}
72
73TEST(RecordingCanvas, emptyClipRect) {
74    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
75        canvas.save(SaveFlags::MatrixClip);
76        canvas.clipRect(0, 0, 100, 100, kIntersect_SkClipOp);
77        canvas.clipRect(100, 100, 200, 200, kIntersect_SkClipOp);
78        canvas.drawRect(0, 0, 50, 50, SkPaint()); // rejected at record time
79        canvas.restore();
80    });
81    ASSERT_EQ(0u, dl->getOps().size()) << "Must be zero ops. Rect should be rejected.";
82}
83
84TEST(RecordingCanvas, emptyPaintRejection) {
85    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
86        SkPaint emptyPaint;
87        emptyPaint.setColor(Color::Transparent);
88
89        float points[] = {0, 0, 200, 200};
90        canvas.drawPoints(points, 4, emptyPaint);
91        canvas.drawLines(points, 4, emptyPaint);
92        canvas.drawRect(0, 0, 200, 200, emptyPaint);
93        canvas.drawRegion(SkRegion(SkIRect::MakeWH(200, 200)), emptyPaint);
94        canvas.drawRoundRect(0, 0, 200, 200, 10, 10, emptyPaint);
95        canvas.drawCircle(100, 100, 100, emptyPaint);
96        canvas.drawOval(0, 0, 200, 200, emptyPaint);
97        canvas.drawArc(0, 0, 200, 200, 0, 360, true, emptyPaint);
98        SkPath path;
99        path.addRect(0, 0, 200, 200);
100        canvas.drawPath(path, emptyPaint);
101    });
102    EXPECT_EQ(0u, dl->getOps().size()) << "Op should be rejected";
103}
104
105TEST(RecordingCanvas, drawArc) {
106    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
107        canvas.drawArc(0, 0, 200, 200, 0, 180, true, SkPaint());
108        canvas.drawArc(0, 0, 100, 100, 0, 360, true, SkPaint());
109    });
110
111    auto&& ops = dl->getOps();
112    ASSERT_EQ(2u, ops.size()) << "Must be exactly two ops";
113    EXPECT_EQ(RecordedOpId::ArcOp, ops[0]->opId);
114    EXPECT_EQ(Rect(200, 200), ops[0]->unmappedBounds);
115
116    EXPECT_EQ(RecordedOpId::OvalOp, ops[1]->opId)
117            << "Circular arcs should be converted to ovals";
118    EXPECT_EQ(Rect(100, 100), ops[1]->unmappedBounds);
119}
120
121TEST(RecordingCanvas, drawLines) {
122    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
123        SkPaint paint;
124        paint.setStrokeWidth(20); // doesn't affect recorded bounds - would be resolved at bake time
125        float points[] = { 0, 0, 20, 10, 30, 40, 90 }; // NB: only 1 valid line
126        canvas.drawLines(&points[0], 7, paint);
127    });
128
129    ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
130    auto op = dl->getOps()[0];
131    ASSERT_EQ(RecordedOpId::LinesOp, op->opId);
132    EXPECT_EQ(4, ((LinesOp*)op)->floatCount)
133            << "float count must be rounded down to closest multiple of 4";
134    EXPECT_EQ(Rect(20, 10), op->unmappedBounds)
135            << "unmapped bounds must be size of line, and not outset for stroke width";
136}
137
138TEST(RecordingCanvas, drawRect) {
139    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
140        canvas.drawRect(10, 20, 90, 180, SkPaint());
141    });
142
143    ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
144    auto op = *(dl->getOps()[0]);
145    ASSERT_EQ(RecordedOpId::RectOp, op.opId);
146    EXPECT_EQ(nullptr, op.localClip);
147    EXPECT_EQ(Rect(10, 20, 90, 180), op.unmappedBounds);
148}
149
150TEST(RecordingCanvas, drawRoundRect) {
151    // Round case - stays rounded
152    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
153        canvas.drawRoundRect(0, 0, 100, 100, 10, 10, SkPaint());
154    });
155    ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
156    ASSERT_EQ(RecordedOpId::RoundRectOp, dl->getOps()[0]->opId);
157
158    // Non-rounded case - turned into drawRect
159    dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
160        canvas.drawRoundRect(0, 0, 100, 100, 0, -1, SkPaint());
161    });
162    ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
163    ASSERT_EQ(RecordedOpId::RectOp, dl->getOps()[0]->opId)
164        << "Non-rounded rects should be converted";
165}
166
167TEST(RecordingCanvas, drawGlyphs) {
168    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
169        SkPaint paint;
170        paint.setAntiAlias(true);
171        paint.setTextSize(20);
172        paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
173        TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
174    });
175
176    int count = 0;
177    playbackOps(*dl, [&count](const RecordedOp& op) {
178        count++;
179        ASSERT_EQ(RecordedOpId::TextOp, op.opId);
180        EXPECT_EQ(nullptr, op.localClip);
181        EXPECT_TRUE(op.localMatrix.isIdentity());
182        EXPECT_TRUE(op.unmappedBounds.contains(25, 15, 50, 25))
183                << "Op expected to be 25+ pixels wide, 10+ pixels tall";
184    });
185    ASSERT_EQ(1, count);
186}
187
188TEST(RecordingCanvas, drawGlyphs_strikeThruAndUnderline) {
189    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
190        SkPaint paint;
191        paint.setAntiAlias(true);
192        paint.setTextSize(20);
193        paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
194        for (int i = 0; i < 2; i++) {
195            for (int j = 0; j < 2; j++) {
196                paint.setUnderlineText(i != 0);
197                paint.setStrikeThruText(j != 0);
198                TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
199            }
200        }
201    });
202
203    auto ops = dl->getOps();
204    ASSERT_EQ(8u, ops.size());
205
206    int index = 0;
207    EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); // no underline or strikethrough
208
209    EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId);
210    EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough only
211
212    EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId);
213    EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline only
214
215    EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId);
216    EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline
217    EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough
218}
219
220TEST(RecordingCanvas, drawGlyphs_forceAlignLeft) {
221    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
222        SkPaint paint;
223        paint.setAntiAlias(true);
224        paint.setTextSize(20);
225        paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
226        paint.setTextAlign(SkPaint::kLeft_Align);
227        TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
228        paint.setTextAlign(SkPaint::kCenter_Align);
229        TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
230        paint.setTextAlign(SkPaint::kRight_Align);
231        TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
232    });
233
234    int count = 0;
235    float lastX = FLT_MAX;
236    playbackOps(*dl, [&count, &lastX](const RecordedOp& op) {
237        count++;
238        ASSERT_EQ(RecordedOpId::TextOp, op.opId);
239        EXPECT_EQ(SkPaint::kLeft_Align, op.paint->getTextAlign())
240                << "recorded drawText commands must force kLeft_Align on their paint";
241
242        // verify TestUtils alignment offsetting (TODO: move asserts to Canvas base class)
243        EXPECT_GT(lastX, ((const TextOp&)op).x)
244                << "x coordinate should reduce across each of the draw commands, from alignment";
245        lastX = ((const TextOp&)op).x;
246    });
247    ASSERT_EQ(3, count);
248}
249
250TEST(RecordingCanvas, drawColor) {
251    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
252        canvas.drawColor(Color::Black, SkBlendMode::kSrcOver);
253    });
254
255    ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
256    auto op = *(dl->getOps()[0]);
257    EXPECT_EQ(RecordedOpId::ColorOp, op.opId);
258    EXPECT_EQ(nullptr, op.localClip);
259    EXPECT_TRUE(op.unmappedBounds.isEmpty()) << "Expect undefined recorded bounds";
260}
261
262TEST(RecordingCanvas, backgroundAndImage) {
263    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
264        sk_sp<Bitmap> bitmap(TestUtils::createBitmap(25, 25));
265        SkPaint paint;
266        paint.setColor(SK_ColorBLUE);
267
268        canvas.save(SaveFlags::MatrixClip);
269        {
270            // a background!
271            canvas.save(SaveFlags::MatrixClip);
272            canvas.drawRect(0, 0, 100, 200, paint);
273            canvas.restore();
274        }
275        {
276            // an image!
277            canvas.save(SaveFlags::MatrixClip);
278            canvas.translate(25, 25);
279            canvas.scale(2, 2);
280            canvas.drawBitmap(*bitmap, 0, 0, nullptr);
281            canvas.restore();
282        }
283        canvas.restore();
284    });
285
286    int count = 0;
287    playbackOps(*dl, [&count](const RecordedOp& op) {
288        if (count == 0) {
289            ASSERT_EQ(RecordedOpId::RectOp, op.opId);
290            ASSERT_NE(nullptr, op.paint);
291            EXPECT_EQ(SK_ColorBLUE, op.paint->getColor());
292            EXPECT_EQ(Rect(100, 200), op.unmappedBounds);
293            EXPECT_EQ(nullptr, op.localClip);
294
295            Matrix4 expectedMatrix;
296            expectedMatrix.loadIdentity();
297            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
298        } else {
299            ASSERT_EQ(RecordedOpId::BitmapOp, op.opId);
300            EXPECT_EQ(nullptr, op.paint);
301            EXPECT_EQ(Rect(25, 25), op.unmappedBounds);
302            EXPECT_EQ(nullptr, op.localClip);
303
304            Matrix4 expectedMatrix;
305            expectedMatrix.loadTranslate(25, 25, 0);
306            expectedMatrix.scale(2, 2, 1);
307            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
308        }
309        count++;
310    });
311    ASSERT_EQ(2, count);
312}
313
314RENDERTHREAD_TEST(RecordingCanvas, textureLayer) {
315    auto layerUpdater = TestUtils::createTextureLayerUpdater(renderThread, 100, 100,
316            SkMatrix::MakeTrans(5, 5));
317
318    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200,
319            [&layerUpdater](RecordingCanvas& canvas) {
320        canvas.drawLayer(layerUpdater.get());
321    });
322
323    validateSingleOp(dl, [] (const RecordedOp& op) {
324        ASSERT_EQ(RecordedOpId::TextureLayerOp, op.opId);
325        ASSERT_TRUE(op.localMatrix.isIdentity()) << "Op must not apply matrix at record time.";
326    });
327}
328
329TEST(RecordingCanvas, saveLayer_simple) {
330    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
331        canvas.saveLayerAlpha(10, 20, 190, 180, 128, SaveFlags::ClipToLayer);
332        canvas.drawRect(10, 20, 190, 180, SkPaint());
333        canvas.restore();
334    });
335    int count = 0;
336    playbackOps(*dl, [&count](const RecordedOp& op) {
337        Matrix4 expectedMatrix;
338        switch(count++) {
339        case 0:
340            EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId);
341            EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
342            EXPECT_EQ(nullptr, op.localClip);
343            EXPECT_TRUE(op.localMatrix.isIdentity());
344            break;
345        case 1:
346            EXPECT_EQ(RecordedOpId::RectOp, op.opId);
347            EXPECT_CLIP_RECT(Rect(180, 160), op.localClip);
348            EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
349            expectedMatrix.loadTranslate(-10, -20, 0);
350            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
351            break;
352        case 2:
353            EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId);
354            // Don't bother asserting recording state data - it's not used
355            break;
356        default:
357            ADD_FAILURE();
358        }
359    });
360    EXPECT_EQ(3, count);
361}
362
363TEST(RecordingCanvas, saveLayer_rounding) {
364    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) {
365            canvas.saveLayerAlpha(10.25f, 10.75f, 89.25f, 89.75f, 128, SaveFlags::ClipToLayer);
366            canvas.drawRect(20, 20, 80, 80, SkPaint());
367            canvas.restore();
368        });
369        int count = 0;
370        playbackOps(*dl, [&count](const RecordedOp& op) {
371            Matrix4 expectedMatrix;
372            switch(count++) {
373            case 0:
374                EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId);
375                EXPECT_EQ(Rect(10, 10, 90, 90), op.unmappedBounds) << "Expect bounds rounded out";
376                break;
377            case 1:
378                EXPECT_EQ(RecordedOpId::RectOp, op.opId);
379                expectedMatrix.loadTranslate(-10, -10, 0);
380                EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix) << "Expect rounded offset";
381                break;
382            case 2:
383                EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId);
384                // Don't bother asserting recording state data - it's not used
385                break;
386            default:
387                ADD_FAILURE();
388            }
389        });
390        EXPECT_EQ(3, count);
391}
392
393TEST(RecordingCanvas, saveLayer_missingRestore) {
394    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
395        canvas.saveLayerAlpha(0, 0, 200, 200, 128, SaveFlags::ClipToLayer);
396        canvas.drawRect(0, 0, 200, 200, SkPaint());
397        // Note: restore omitted, shouldn't result in unmatched save
398    });
399    int count = 0;
400    playbackOps(*dl, [&count](const RecordedOp& op) {
401        if (count++ == 2) {
402            EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId);
403        }
404    });
405    EXPECT_EQ(3, count) << "Missing a restore shouldn't result in an unmatched saveLayer";
406}
407
408TEST(RecordingCanvas, saveLayer_simpleUnclipped) {
409    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
410        canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SaveFlags::Flags)0); // unclipped
411        canvas.drawRect(10, 20, 190, 180, SkPaint());
412        canvas.restore();
413    });
414    int count = 0;
415    playbackOps(*dl, [&count](const RecordedOp& op) {
416        switch(count++) {
417        case 0:
418            EXPECT_EQ(RecordedOpId::BeginUnclippedLayerOp, op.opId);
419            EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
420            EXPECT_EQ(nullptr, op.localClip);
421            EXPECT_TRUE(op.localMatrix.isIdentity());
422            break;
423        case 1:
424            EXPECT_EQ(RecordedOpId::RectOp, op.opId);
425            EXPECT_EQ(nullptr, op.localClip);
426            EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
427            EXPECT_TRUE(op.localMatrix.isIdentity());
428            break;
429        case 2:
430            EXPECT_EQ(RecordedOpId::EndUnclippedLayerOp, op.opId);
431            // Don't bother asserting recording state data - it's not used
432            break;
433        default:
434            ADD_FAILURE();
435        }
436    });
437    EXPECT_EQ(3, count);
438}
439
440TEST(RecordingCanvas, saveLayer_addClipFlag) {
441    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
442        canvas.save(SaveFlags::MatrixClip);
443        canvas.clipRect(10, 20, 190, 180, kIntersect_SkClipOp);
444        canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SaveFlags::Flags)0); // unclipped
445        canvas.drawRect(10, 20, 190, 180, SkPaint());
446        canvas.restore();
447        canvas.restore();
448    });
449    int count = 0;
450    playbackOps(*dl, [&count](const RecordedOp& op) {
451        if (count++ == 0) {
452            EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId)
453                    << "Clip + unclipped saveLayer should result in a clipped layer";
454        }
455    });
456    EXPECT_EQ(3, count);
457}
458
459TEST(RecordingCanvas, saveLayer_viewportCrop) {
460    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
461        // shouldn't matter, since saveLayer will clip to its bounds
462        canvas.clipRect(-1000, -1000, 1000, 1000, kReplace_SkClipOp);
463
464        canvas.saveLayerAlpha(100, 100, 300, 300, 128, SaveFlags::ClipToLayer);
465        canvas.drawRect(0, 0, 400, 400, SkPaint());
466        canvas.restore();
467    });
468    int count = 0;
469    playbackOps(*dl, [&count](const RecordedOp& op) {
470        if (count++ == 1) {
471            Matrix4 expectedMatrix;
472            EXPECT_EQ(RecordedOpId::RectOp, op.opId);
473            EXPECT_CLIP_RECT(Rect(100, 100), op.localClip) // Recorded clip rect should be
474            // intersection of viewport and saveLayer bounds, in layer space;
475            EXPECT_EQ(Rect(400, 400), op.unmappedBounds);
476            expectedMatrix.loadTranslate(-100, -100, 0);
477            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
478        }
479    });
480    EXPECT_EQ(3, count);
481}
482
483TEST(RecordingCanvas, saveLayer_rotateUnclipped) {
484    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
485        canvas.save(SaveFlags::MatrixClip);
486        canvas.translate(100, 100);
487        canvas.rotate(45);
488        canvas.translate(-50, -50);
489
490        canvas.saveLayerAlpha(0, 0, 100, 100, 128, SaveFlags::ClipToLayer);
491        canvas.drawRect(0, 0, 100, 100, SkPaint());
492        canvas.restore();
493
494        canvas.restore();
495    });
496    int count = 0;
497    playbackOps(*dl, [&count](const RecordedOp& op) {
498        if (count++ == 1) {
499            EXPECT_EQ(RecordedOpId::RectOp, op.opId);
500            EXPECT_CLIP_RECT(Rect(100, 100), op.localClip);
501            EXPECT_EQ(Rect(100, 100), op.unmappedBounds);
502            EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), op.localMatrix)
503                    << "Recorded op shouldn't see any canvas transform before the saveLayer";
504        }
505    });
506    EXPECT_EQ(3, count);
507}
508
509TEST(RecordingCanvas, saveLayer_rotateClipped) {
510    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
511        canvas.save(SaveFlags::MatrixClip);
512        canvas.translate(100, 100);
513        canvas.rotate(45);
514        canvas.translate(-200, -200);
515
516        // area of saveLayer will be clipped to parent viewport, so we ask for 400x400...
517        canvas.saveLayerAlpha(0, 0, 400, 400, 128, SaveFlags::ClipToLayer);
518        canvas.drawRect(0, 0, 400, 400, SkPaint());
519        canvas.restore();
520
521        canvas.restore();
522    });
523    int count = 0;
524    playbackOps(*dl, [&count](const RecordedOp& op) {
525        if (count++ == 1) {
526            Matrix4 expectedMatrix;
527            EXPECT_EQ(RecordedOpId::RectOp, op.opId);
528
529            // ...and get about 58.6, 58.6, 341.4 341.4, because the bounds are clipped by
530            // the parent 200x200 viewport, but prior to rotation
531            ASSERT_NE(nullptr, op.localClip);
532            ASSERT_EQ(ClipMode::Rectangle, op.localClip->mode);
533            // NOTE: this check relies on saveLayer altering the clip post-viewport init. This
534            // causes the clip to be recorded by contained draw commands, though it's not necessary
535            // since the same clip will be computed at draw time. If such a change is made, this
536            // check could be done at record time by querying the clip, or the clip could be altered
537            // slightly so that it is serialized.
538            EXPECT_EQ(Rect(59, 59, 341, 341), op.localClip->rect);
539            EXPECT_EQ(Rect(400, 400), op.unmappedBounds);
540            expectedMatrix.loadIdentity();
541            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
542        }
543    });
544    EXPECT_EQ(3, count);
545}
546
547TEST(RecordingCanvas, saveLayer_rejectBegin) {
548    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
549        canvas.save(SaveFlags::MatrixClip);
550        canvas.translate(0, -20); // avoid identity case
551        // empty clip rect should force layer + contents to be rejected
552        canvas.clipRect(0, -20, 200, -20, kIntersect_SkClipOp);
553        canvas.saveLayerAlpha(0, 0, 200, 200, 128, SaveFlags::ClipToLayer);
554        canvas.drawRect(0, 0, 200, 200, SkPaint());
555        canvas.restore();
556        canvas.restore();
557    });
558
559    ASSERT_EQ(0u, dl->getOps().size()) << "Begin/Rect/End should all be rejected.";
560}
561
562TEST(RecordingCanvas, drawRenderNode_rejection) {
563    auto child = TestUtils::createNode(50, 50, 150, 150,
564            [](RenderProperties& props, Canvas& canvas) {
565        SkPaint paint;
566        paint.setColor(SK_ColorWHITE);
567        canvas.drawRect(0, 0, 100, 100, paint);
568    });
569
570    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [&child](RecordingCanvas& canvas) {
571        canvas.clipRect(0, 0, 0, 0, kIntersect_SkClipOp); // empty clip, reject node
572        canvas.drawRenderNode(child.get()); // shouldn't crash when rejecting node...
573    });
574    ASSERT_TRUE(dl->isEmpty());
575}
576
577TEST(RecordingCanvas, drawRenderNode_projection) {
578    sp<RenderNode> background = TestUtils::createNode(50, 50, 150, 150,
579            [](RenderProperties& props, Canvas& canvas) {
580        SkPaint paint;
581        paint.setColor(SK_ColorWHITE);
582        canvas.drawRect(0, 0, 100, 100, paint);
583    });
584    {
585        background->mutateStagingProperties().setProjectionReceiver(false);
586
587        // NO RECEIVER PRESENT
588        auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200,
589                    [&background](RecordingCanvas& canvas) {
590            canvas.drawRect(0, 0, 100, 100, SkPaint());
591            canvas.drawRenderNode(background.get());
592            canvas.drawRect(0, 0, 100, 100, SkPaint());
593        });
594        EXPECT_EQ(-1, dl->projectionReceiveIndex)
595                << "no projection receiver should have been observed";
596    }
597    {
598        background->mutateStagingProperties().setProjectionReceiver(true);
599
600        // RECEIVER PRESENT
601        auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200,
602                    [&background](RecordingCanvas& canvas) {
603            canvas.drawRect(0, 0, 100, 100, SkPaint());
604            canvas.drawRenderNode(background.get());
605            canvas.drawRect(0, 0, 100, 100, SkPaint());
606        });
607
608        ASSERT_EQ(3u, dl->getOps().size()) << "Must be three ops";
609        auto op = dl->getOps()[1];
610        EXPECT_EQ(RecordedOpId::RenderNodeOp, op->opId);
611        EXPECT_EQ(1, dl->projectionReceiveIndex)
612                << "correct projection receiver not identified";
613
614        // verify the behavior works even though projection receiver hasn't been sync'd yet
615        EXPECT_TRUE(background->stagingProperties().isProjectionReceiver());
616        EXPECT_FALSE(background->properties().isProjectionReceiver());
617    }
618}
619
620TEST(RecordingCanvas, firstClipWillReplace) {
621    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
622        canvas.save(SaveFlags::MatrixClip);
623        // since no explicit clip set on canvas, this should be the one observed on op:
624        canvas.clipRect(-100, -100, 300, 300, kIntersect_SkClipOp);
625
626        SkPaint paint;
627        paint.setColor(SK_ColorWHITE);
628        canvas.drawRect(0, 0, 100, 100, paint);
629
630        canvas.restore();
631    });
632    ASSERT_EQ(1u, dl->getOps().size()) << "Must have one op";
633    // first clip must be preserved, even if it extends beyond canvas bounds
634    EXPECT_CLIP_RECT(Rect(-100, -100, 300, 300), dl->getOps()[0]->localClip);
635}
636
637TEST(RecordingCanvas, replaceClipIntersectWithRoot) {
638    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) {
639        canvas.save(SaveFlags::MatrixClip);
640        canvas.clipRect(-10, -10, 110, 110, kReplace_SkClipOp);
641        canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
642        canvas.restore();
643    });
644    ASSERT_EQ(1u, dl->getOps().size()) << "Must have one op";
645    // first clip must be preserved, even if it extends beyond canvas bounds
646    EXPECT_CLIP_RECT(Rect(-10, -10, 110, 110), dl->getOps()[0]->localClip);
647    EXPECT_TRUE(dl->getOps()[0]->localClip->intersectWithRoot);
648}
649
650TEST(RecordingCanvas, insertReorderBarrier) {
651    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
652        canvas.drawRect(0, 0, 400, 400, SkPaint());
653        canvas.insertReorderBarrier(true);
654        canvas.insertReorderBarrier(false);
655        canvas.insertReorderBarrier(false);
656        canvas.insertReorderBarrier(true);
657        canvas.drawRect(0, 0, 400, 400, SkPaint());
658        canvas.insertReorderBarrier(false);
659    });
660
661    auto chunks = dl->getChunks();
662    EXPECT_EQ(0u, chunks[0].beginOpIndex);
663    EXPECT_EQ(1u, chunks[0].endOpIndex);
664    EXPECT_FALSE(chunks[0].reorderChildren);
665
666    EXPECT_EQ(1u, chunks[1].beginOpIndex);
667    EXPECT_EQ(2u, chunks[1].endOpIndex);
668    EXPECT_TRUE(chunks[1].reorderChildren);
669}
670
671TEST(RecordingCanvas, insertReorderBarrier_clip) {
672    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
673        // first chunk: no recorded clip
674        canvas.insertReorderBarrier(true);
675        canvas.drawRect(0, 0, 400, 400, SkPaint());
676
677        // second chunk: no recorded clip, since inorder region
678        canvas.clipRect(0, 0, 200, 200, kIntersect_SkClipOp);
679        canvas.insertReorderBarrier(false);
680        canvas.drawRect(0, 0, 400, 400, SkPaint());
681
682        // third chunk: recorded clip
683        canvas.insertReorderBarrier(true);
684        canvas.drawRect(0, 0, 400, 400, SkPaint());
685    });
686
687    auto chunks = dl->getChunks();
688    ASSERT_EQ(3u, chunks.size());
689
690    EXPECT_TRUE(chunks[0].reorderChildren);
691    EXPECT_EQ(nullptr, chunks[0].reorderClip);
692
693    EXPECT_FALSE(chunks[1].reorderChildren);
694    EXPECT_EQ(nullptr, chunks[1].reorderClip);
695
696    EXPECT_TRUE(chunks[2].reorderChildren);
697    ASSERT_NE(nullptr, chunks[2].reorderClip);
698    EXPECT_EQ(Rect(200, 200), chunks[2].reorderClip->rect);
699}
700
701TEST(RecordingCanvas, refPaint) {
702    SkPaint paint;
703
704    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [&paint](RecordingCanvas& canvas) {
705        paint.setColor(SK_ColorBLUE);
706        // first two should use same paint
707        canvas.drawRect(0, 0, 200, 10, paint);
708        SkPaint paintCopy(paint);
709        canvas.drawRect(0, 10, 200, 20, paintCopy);
710
711        // only here do we use different paint ptr
712        paint.setColor(SK_ColorRED);
713        canvas.drawRect(0, 20, 200, 30, paint);
714    });
715    auto ops = dl->getOps();
716    ASSERT_EQ(3u, ops.size());
717
718    // first two are the same
719    EXPECT_NE(nullptr, ops[0]->paint);
720    EXPECT_NE(&paint, ops[0]->paint);
721    EXPECT_EQ(ops[0]->paint, ops[1]->paint);
722
723    // last is different, but still copied / non-null
724    EXPECT_NE(nullptr, ops[2]->paint);
725    EXPECT_NE(ops[0]->paint, ops[2]->paint);
726    EXPECT_NE(&paint, ops[2]->paint);
727}
728
729TEST(RecordingCanvas, refBitmap) {
730    sk_sp<Bitmap> bitmap(TestUtils::createBitmap(100, 100));
731    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [&bitmap](RecordingCanvas& canvas) {
732        canvas.drawBitmap(*bitmap, 0, 0, nullptr);
733    });
734    auto& bitmaps = dl->getBitmapResources();
735    EXPECT_EQ(1u, bitmaps.size());
736}
737
738TEST(RecordingCanvas, refBitmapInShader_bitmapShader) {
739    sk_sp<Bitmap> bitmap = TestUtils::createBitmap(100, 100);
740    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [&bitmap](RecordingCanvas& canvas) {
741        SkPaint paint;
742        SkBitmap skBitmap;
743        bitmap->getSkBitmap(&skBitmap);
744        sk_sp<SkShader> shader = SkMakeBitmapShader(skBitmap,
745                SkShader::TileMode::kClamp_TileMode,
746                SkShader::TileMode::kClamp_TileMode,
747                nullptr,
748                kNever_SkCopyPixelsMode,
749                nullptr);
750        paint.setShader(std::move(shader));
751        canvas.drawRoundRect(0, 0, 100, 100, 20.0f, 20.0f, paint);
752    });
753    auto& bitmaps = dl->getBitmapResources();
754    EXPECT_EQ(1u, bitmaps.size());
755}
756
757TEST(RecordingCanvas, refBitmapInShader_composeShader) {
758    sk_sp<Bitmap> bitmap = TestUtils::createBitmap(100, 100);
759    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [&bitmap](RecordingCanvas& canvas) {
760        SkPaint paint;
761        SkBitmap skBitmap;
762        bitmap->getSkBitmap(&skBitmap);
763        sk_sp<SkShader> shader1 = SkMakeBitmapShader(skBitmap,
764                SkShader::TileMode::kClamp_TileMode,
765                SkShader::TileMode::kClamp_TileMode,
766                nullptr,
767                kNever_SkCopyPixelsMode,
768                nullptr);
769
770        SkPoint center;
771        center.set(50, 50);
772        SkColor colors[2];
773        colors[0] = Color::Black;
774        colors[1] = Color::White;
775        sk_sp<SkShader> shader2 = SkGradientShader::MakeRadial(center, 50, colors, nullptr, 2,
776                SkShader::TileMode::kRepeat_TileMode);
777
778        sk_sp<SkShader> composeShader = SkShader::MakeComposeShader(std::move(shader1), std::move(shader2),
779                SkBlendMode::kMultiply);
780        paint.setShader(std::move(composeShader));
781        canvas.drawRoundRect(0, 0, 100, 100, 20.0f, 20.0f, paint);
782    });
783    auto& bitmaps = dl->getBitmapResources();
784    EXPECT_EQ(1u, bitmaps.size());
785}
786
787TEST(RecordingCanvas, drawText) {
788    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
789        Paint paint;
790        paint.setAntiAlias(true);
791        paint.setTextSize(20);
792        paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
793        std::unique_ptr<uint16_t[]> dst = TestUtils::asciiToUtf16("HELLO");
794        canvas.drawText(dst.get(), 0, 5, 5, 25, 25, minikin::kBidi_Force_LTR, paint, NULL);
795    });
796
797    int count = 0;
798    playbackOps(*dl, [&count](const RecordedOp& op) {
799        count++;
800        ASSERT_EQ(RecordedOpId::TextOp, op.opId);
801        EXPECT_EQ(nullptr, op.localClip);
802        EXPECT_TRUE(op.localMatrix.isIdentity());
803        EXPECT_TRUE(op.unmappedBounds.getHeight() >= 10);
804        EXPECT_TRUE(op.unmappedBounds.getWidth() >= 25);
805    });
806    ASSERT_EQ(1, count);
807}
808
809TEST(RecordingCanvas, drawTextInHighContrast) {
810    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
811        canvas.setHighContrastText(true);
812        Paint paint;
813        paint.setColor(SK_ColorWHITE);
814        paint.setAntiAlias(true);
815        paint.setTextSize(20);
816        paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
817        std::unique_ptr<uint16_t[]> dst = TestUtils::asciiToUtf16("HELLO");
818        canvas.drawText(dst.get(), 0, 5, 5, 25, 25, minikin::kBidi_Force_LTR, paint, NULL);
819    });
820
821    int count = 0;
822    playbackOps(*dl, [&count](const RecordedOp& op) {
823        ASSERT_EQ(RecordedOpId::TextOp, op.opId);
824        if (count++ == 0) {
825            EXPECT_EQ(SK_ColorBLACK, op.paint->getColor());
826            EXPECT_EQ(SkPaint::kStrokeAndFill_Style, op.paint->getStyle());
827        } else {
828            EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
829            EXPECT_EQ(SkPaint::kFill_Style, op.paint->getStyle());
830        }
831
832    });
833    ASSERT_EQ(2, count);
834}
835
836} // namespace uirenderer
837} // namespace android
838