RecordingCanvasTests.cpp revision 79abbf22d4f672208327546661e694d837f564a9
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 <RecordedOp.h>
20#include <RecordingCanvas.h>
21#include <hwui/Paint.h>
22#include <minikin/Layout.h>
23#include <tests/common/TestUtils.h>
24#include <utils/Color.h>
25
26#include <SkGradientShader.h>
27#include <SkShader.h>
28
29namespace android {
30namespace uirenderer {
31
32static void playbackOps(const DisplayList& displayList,
33        std::function<void(const RecordedOp&)> opReceiver) {
34    for (auto& chunk : displayList.getChunks()) {
35        for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
36            RecordedOp* op = displayList.getOps()[opIndex];
37            opReceiver(*op);
38        }
39    }
40}
41
42TEST(RecordingCanvas, emptyPlayback) {
43    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
44        canvas.save(SaveFlags::MatrixClip);
45        canvas.restore();
46    });
47    playbackOps(*dl, [](const RecordedOp& op) { ADD_FAILURE(); });
48}
49
50TEST(RecordingCanvas, clipRect) {
51    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) {
52        canvas.save(SaveFlags::MatrixClip);
53        canvas.clipRect(0, 0, 100, 100, SkRegion::kIntersect_Op);
54        canvas.drawRect(0, 0, 50, 50, SkPaint());
55        canvas.drawRect(50, 50, 100, 100, SkPaint());
56        canvas.restore();
57    });
58
59    ASSERT_EQ(2u, dl->getOps().size()) << "Must be exactly two ops";
60    EXPECT_CLIP_RECT(Rect(100, 100), dl->getOps()[0]->localClip);
61    EXPECT_CLIP_RECT(Rect(100, 100), dl->getOps()[1]->localClip);
62    EXPECT_EQ(dl->getOps()[0]->localClip, dl->getOps()[1]->localClip)
63            << "Clip should be serialized once";
64}
65
66TEST(RecordingCanvas, emptyClipRect) {
67    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
68        canvas.save(SaveFlags::MatrixClip);
69        canvas.clipRect(0, 0, 100, 100, SkRegion::kIntersect_Op);
70        canvas.clipRect(100, 100, 200, 200, SkRegion::kIntersect_Op);
71        canvas.drawRect(0, 0, 50, 50, SkPaint()); // rejected at record time
72        canvas.restore();
73    });
74    ASSERT_EQ(0u, dl->getOps().size()) << "Must be zero ops. Rect should be rejected.";
75}
76
77TEST(RecordingCanvas, drawArc) {
78    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
79        canvas.drawArc(0, 0, 200, 200, 0, 180, true, SkPaint());
80        canvas.drawArc(0, 0, 100, 100, 0, 360, true, SkPaint());
81    });
82
83    auto&& ops = dl->getOps();
84    ASSERT_EQ(2u, ops.size()) << "Must be exactly two ops";
85    EXPECT_EQ(RecordedOpId::ArcOp, ops[0]->opId);
86    EXPECT_EQ(Rect(200, 200), ops[0]->unmappedBounds);
87
88    EXPECT_EQ(RecordedOpId::OvalOp, ops[1]->opId)
89            << "Circular arcs should be converted to ovals";
90    EXPECT_EQ(Rect(100, 100), ops[1]->unmappedBounds);
91}
92
93TEST(RecordingCanvas, drawLines) {
94    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
95        SkPaint paint;
96        paint.setStrokeWidth(20); // doesn't affect recorded bounds - would be resolved at bake time
97        float points[] = { 0, 0, 20, 10, 30, 40, 90 }; // NB: only 1 valid line
98        canvas.drawLines(&points[0], 7, paint);
99    });
100
101    ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
102    auto op = dl->getOps()[0];
103    ASSERT_EQ(RecordedOpId::LinesOp, op->opId);
104    EXPECT_EQ(4, ((LinesOp*)op)->floatCount)
105            << "float count must be rounded down to closest multiple of 4";
106    EXPECT_EQ(Rect(20, 10), op->unmappedBounds)
107            << "unmapped bounds must be size of line, and not outset for stroke width";
108}
109
110TEST(RecordingCanvas, drawRect) {
111    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
112        canvas.drawRect(10, 20, 90, 180, SkPaint());
113    });
114
115    ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
116    auto op = *(dl->getOps()[0]);
117    ASSERT_EQ(RecordedOpId::RectOp, op.opId);
118    EXPECT_EQ(nullptr, op.localClip);
119    EXPECT_EQ(Rect(10, 20, 90, 180), op.unmappedBounds);
120}
121
122TEST(RecordingCanvas, drawRoundRect) {
123    // Round case - stays rounded
124    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
125        canvas.drawRoundRect(0, 0, 100, 100, 10, 10, SkPaint());
126    });
127    ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
128    ASSERT_EQ(RecordedOpId::RoundRectOp, dl->getOps()[0]->opId);
129
130    // Non-rounded case - turned into drawRect
131    dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
132        canvas.drawRoundRect(0, 0, 100, 100, 0, -1, SkPaint());
133    });
134    ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
135    ASSERT_EQ(RecordedOpId::RectOp, dl->getOps()[0]->opId)
136        << "Non-rounded rects should be converted";
137}
138
139TEST(RecordingCanvas, drawGlyphs) {
140    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
141        SkPaint paint;
142        paint.setAntiAlias(true);
143        paint.setTextSize(20);
144        paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
145        TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
146    });
147
148    int count = 0;
149    playbackOps(*dl, [&count](const RecordedOp& op) {
150        count++;
151        ASSERT_EQ(RecordedOpId::TextOp, op.opId);
152        EXPECT_EQ(nullptr, op.localClip);
153        EXPECT_TRUE(op.localMatrix.isIdentity());
154        EXPECT_TRUE(op.unmappedBounds.contains(25, 15, 50, 25))
155                << "Op expected to be 25+ pixels wide, 10+ pixels tall";
156    });
157    ASSERT_EQ(1, count);
158}
159
160TEST(RecordingCanvas, drawGlyphs_strikeThruAndUnderline) {
161    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
162        SkPaint paint;
163        paint.setAntiAlias(true);
164        paint.setTextSize(20);
165        paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
166        for (int i = 0; i < 2; i++) {
167            for (int j = 0; j < 2; j++) {
168                paint.setUnderlineText(i != 0);
169                paint.setStrikeThruText(j != 0);
170                TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
171            }
172        }
173    });
174
175    auto ops = dl->getOps();
176    ASSERT_EQ(8u, ops.size());
177
178    int index = 0;
179    EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); // no underline or strikethrough
180
181    EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId);
182    EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough only
183
184    EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId);
185    EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline only
186
187    EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId);
188    EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline
189    EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough
190}
191
192TEST(RecordingCanvas, drawGlyphs_forceAlignLeft) {
193    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
194        SkPaint paint;
195        paint.setAntiAlias(true);
196        paint.setTextSize(20);
197        paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
198        paint.setTextAlign(SkPaint::kLeft_Align);
199        TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
200        paint.setTextAlign(SkPaint::kCenter_Align);
201        TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
202        paint.setTextAlign(SkPaint::kRight_Align);
203        TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
204    });
205
206    int count = 0;
207    float lastX = FLT_MAX;
208    playbackOps(*dl, [&count, &lastX](const RecordedOp& op) {
209        count++;
210        ASSERT_EQ(RecordedOpId::TextOp, op.opId);
211        EXPECT_EQ(SkPaint::kLeft_Align, op.paint->getTextAlign())
212                << "recorded drawText commands must force kLeft_Align on their paint";
213
214        // verify TestUtils alignment offsetting (TODO: move asserts to Canvas base class)
215        EXPECT_GT(lastX, ((const TextOp&)op).x)
216                << "x coordinate should reduce across each of the draw commands, from alignment";
217        lastX = ((const TextOp&)op).x;
218    });
219    ASSERT_EQ(3, count);
220}
221
222TEST(RecordingCanvas, drawColor) {
223    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
224        canvas.drawColor(Color::Black, SkXfermode::kSrcOver_Mode);
225    });
226
227    ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
228    auto op = *(dl->getOps()[0]);
229    EXPECT_EQ(RecordedOpId::RectOp, op.opId);
230    EXPECT_EQ(nullptr, op.localClip);
231    EXPECT_TRUE(op.unmappedBounds.contains(Rect(200, 200))) << "Expect recording/clip bounds";
232}
233
234TEST(RecordingCanvas, backgroundAndImage) {
235    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
236        SkBitmap bitmap;
237        bitmap.setInfo(SkImageInfo::MakeUnknown(25, 25));
238        SkPaint paint;
239        paint.setColor(SK_ColorBLUE);
240
241        canvas.save(SaveFlags::MatrixClip);
242        {
243            // a background!
244            canvas.save(SaveFlags::MatrixClip);
245            canvas.drawRect(0, 0, 100, 200, paint);
246            canvas.restore();
247        }
248        {
249            // an image!
250            canvas.save(SaveFlags::MatrixClip);
251            canvas.translate(25, 25);
252            canvas.scale(2, 2);
253            canvas.drawBitmap(bitmap, 0, 0, nullptr);
254            canvas.restore();
255        }
256        canvas.restore();
257    });
258
259    int count = 0;
260    playbackOps(*dl, [&count](const RecordedOp& op) {
261        if (count == 0) {
262            ASSERT_EQ(RecordedOpId::RectOp, op.opId);
263            ASSERT_NE(nullptr, op.paint);
264            EXPECT_EQ(SK_ColorBLUE, op.paint->getColor());
265            EXPECT_EQ(Rect(100, 200), op.unmappedBounds);
266            EXPECT_EQ(nullptr, op.localClip);
267
268            Matrix4 expectedMatrix;
269            expectedMatrix.loadIdentity();
270            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
271        } else {
272            ASSERT_EQ(RecordedOpId::BitmapOp, op.opId);
273            EXPECT_EQ(nullptr, op.paint);
274            EXPECT_EQ(Rect(25, 25), op.unmappedBounds);
275            EXPECT_EQ(nullptr, op.localClip);
276
277            Matrix4 expectedMatrix;
278            expectedMatrix.loadTranslate(25, 25, 0);
279            expectedMatrix.scale(2, 2, 1);
280            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
281        }
282        count++;
283    });
284    ASSERT_EQ(2, count);
285}
286
287TEST(RecordingCanvas, saveLayer_simple) {
288    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
289        canvas.saveLayerAlpha(10, 20, 190, 180, 128, SaveFlags::ClipToLayer);
290        canvas.drawRect(10, 20, 190, 180, SkPaint());
291        canvas.restore();
292    });
293    int count = 0;
294    playbackOps(*dl, [&count](const RecordedOp& op) {
295        Matrix4 expectedMatrix;
296        switch(count++) {
297        case 0:
298            EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId);
299            EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
300            EXPECT_EQ(nullptr, op.localClip);
301            EXPECT_TRUE(op.localMatrix.isIdentity());
302            break;
303        case 1:
304            EXPECT_EQ(RecordedOpId::RectOp, op.opId);
305            EXPECT_CLIP_RECT(Rect(180, 160), op.localClip);
306            EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
307            expectedMatrix.loadTranslate(-10, -20, 0);
308            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
309            break;
310        case 2:
311            EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId);
312            // Don't bother asserting recording state data - it's not used
313            break;
314        default:
315            ADD_FAILURE();
316        }
317    });
318    EXPECT_EQ(3, count);
319}
320
321TEST(RecordingCanvas, saveLayer_missingRestore) {
322    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
323        canvas.saveLayerAlpha(0, 0, 200, 200, 128, SaveFlags::ClipToLayer);
324        canvas.drawRect(0, 0, 200, 200, SkPaint());
325        // Note: restore omitted, shouldn't result in unmatched save
326    });
327    int count = 0;
328    playbackOps(*dl, [&count](const RecordedOp& op) {
329        if (count++ == 2) {
330            EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId);
331        }
332    });
333    EXPECT_EQ(3, count) << "Missing a restore shouldn't result in an unmatched saveLayer";
334}
335
336TEST(RecordingCanvas, saveLayer_simpleUnclipped) {
337    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
338        canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SaveFlags::Flags)0); // unclipped
339        canvas.drawRect(10, 20, 190, 180, SkPaint());
340        canvas.restore();
341    });
342    int count = 0;
343    playbackOps(*dl, [&count](const RecordedOp& op) {
344        switch(count++) {
345        case 0:
346            EXPECT_EQ(RecordedOpId::BeginUnclippedLayerOp, op.opId);
347            EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
348            EXPECT_EQ(nullptr, op.localClip);
349            EXPECT_TRUE(op.localMatrix.isIdentity());
350            break;
351        case 1:
352            EXPECT_EQ(RecordedOpId::RectOp, op.opId);
353            EXPECT_EQ(nullptr, op.localClip);
354            EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
355            EXPECT_TRUE(op.localMatrix.isIdentity());
356            break;
357        case 2:
358            EXPECT_EQ(RecordedOpId::EndUnclippedLayerOp, op.opId);
359            // Don't bother asserting recording state data - it's not used
360            break;
361        default:
362            ADD_FAILURE();
363        }
364    });
365    EXPECT_EQ(3, count);
366}
367
368TEST(RecordingCanvas, saveLayer_addClipFlag) {
369    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
370        canvas.save(SaveFlags::MatrixClip);
371        canvas.clipRect(10, 20, 190, 180, SkRegion::kIntersect_Op);
372        canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SaveFlags::Flags)0); // unclipped
373        canvas.drawRect(10, 20, 190, 180, SkPaint());
374        canvas.restore();
375        canvas.restore();
376    });
377    int count = 0;
378    playbackOps(*dl, [&count](const RecordedOp& op) {
379        if (count++ == 0) {
380            EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId)
381                    << "Clip + unclipped saveLayer should result in a clipped layer";
382        }
383    });
384    EXPECT_EQ(3, count);
385}
386
387TEST(RecordingCanvas, saveLayer_viewportCrop) {
388    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
389        // shouldn't matter, since saveLayer will clip to its bounds
390        canvas.clipRect(-1000, -1000, 1000, 1000, SkRegion::kReplace_Op);
391
392        canvas.saveLayerAlpha(100, 100, 300, 300, 128, SaveFlags::ClipToLayer);
393        canvas.drawRect(0, 0, 400, 400, SkPaint());
394        canvas.restore();
395    });
396    int count = 0;
397    playbackOps(*dl, [&count](const RecordedOp& op) {
398        if (count++ == 1) {
399            Matrix4 expectedMatrix;
400            EXPECT_EQ(RecordedOpId::RectOp, op.opId);
401            EXPECT_CLIP_RECT(Rect(100, 100), op.localClip) // Recorded clip rect should be
402            // intersection of viewport and saveLayer bounds, in layer space;
403            EXPECT_EQ(Rect(400, 400), op.unmappedBounds);
404            expectedMatrix.loadTranslate(-100, -100, 0);
405            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
406        }
407    });
408    EXPECT_EQ(3, count);
409}
410
411TEST(RecordingCanvas, saveLayer_rotateUnclipped) {
412    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
413        canvas.save(SaveFlags::MatrixClip);
414        canvas.translate(100, 100);
415        canvas.rotate(45);
416        canvas.translate(-50, -50);
417
418        canvas.saveLayerAlpha(0, 0, 100, 100, 128, SaveFlags::ClipToLayer);
419        canvas.drawRect(0, 0, 100, 100, SkPaint());
420        canvas.restore();
421
422        canvas.restore();
423    });
424    int count = 0;
425    playbackOps(*dl, [&count](const RecordedOp& op) {
426        if (count++ == 1) {
427            EXPECT_EQ(RecordedOpId::RectOp, op.opId);
428            EXPECT_CLIP_RECT(Rect(100, 100), op.localClip);
429            EXPECT_EQ(Rect(100, 100), op.unmappedBounds);
430            EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), op.localMatrix)
431                    << "Recorded op shouldn't see any canvas transform before the saveLayer";
432        }
433    });
434    EXPECT_EQ(3, count);
435}
436
437TEST(RecordingCanvas, saveLayer_rotateClipped) {
438    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
439        canvas.save(SaveFlags::MatrixClip);
440        canvas.translate(100, 100);
441        canvas.rotate(45);
442        canvas.translate(-200, -200);
443
444        // area of saveLayer will be clipped to parent viewport, so we ask for 400x400...
445        canvas.saveLayerAlpha(0, 0, 400, 400, 128, SaveFlags::ClipToLayer);
446        canvas.drawRect(0, 0, 400, 400, SkPaint());
447        canvas.restore();
448
449        canvas.restore();
450    });
451    int count = 0;
452    playbackOps(*dl, [&count](const RecordedOp& op) {
453        if (count++ == 1) {
454            Matrix4 expectedMatrix;
455            EXPECT_EQ(RecordedOpId::RectOp, op.opId);
456
457            // ...and get about 58.6, 58.6, 341.4 341.4, because the bounds are clipped by
458            // the parent 200x200 viewport, but prior to rotation
459            ASSERT_NE(nullptr, op.localClip);
460            ASSERT_EQ(ClipMode::Rectangle, op.localClip->mode);
461            // NOTE: this check relies on saveLayer altering the clip post-viewport init. This
462            // causes the clip to be recorded by contained draw commands, though it's not necessary
463            // since the same clip will be computed at draw time. If such a change is made, this
464            // check could be done at record time by querying the clip, or the clip could be altered
465            // slightly so that it is serialized.
466            EXPECT_EQ(Rect(59, 59, 341, 341), op.localClip->rect);
467            EXPECT_EQ(Rect(400, 400), op.unmappedBounds);
468            expectedMatrix.loadIdentity();
469            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
470        }
471    });
472    EXPECT_EQ(3, count);
473}
474
475TEST(RecordingCanvas, drawRenderNode_rejection) {
476    auto child = TestUtils::createNode(50, 50, 150, 150,
477            [](RenderProperties& props, RecordingCanvas& canvas) {
478        SkPaint paint;
479        paint.setColor(SK_ColorWHITE);
480        canvas.drawRect(0, 0, 100, 100, paint);
481    });
482
483    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [&child](RecordingCanvas& canvas) {
484        canvas.clipRect(0, 0, 0, 0, SkRegion::kIntersect_Op); // empty clip, reject node
485        canvas.drawRenderNode(child.get()); // shouldn't crash when rejecting node...
486    });
487    ASSERT_TRUE(dl->isEmpty());
488}
489
490TEST(RecordingCanvas, drawRenderNode_projection) {
491    sp<RenderNode> background = TestUtils::createNode(50, 50, 150, 150,
492            [](RenderProperties& props, RecordingCanvas& canvas) {
493        SkPaint paint;
494        paint.setColor(SK_ColorWHITE);
495        canvas.drawRect(0, 0, 100, 100, paint);
496    });
497    {
498        background->mutateStagingProperties().setProjectionReceiver(false);
499
500        // NO RECEIVER PRESENT
501        auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200,
502                    [&background](RecordingCanvas& canvas) {
503            canvas.drawRect(0, 0, 100, 100, SkPaint());
504            canvas.drawRenderNode(background.get());
505            canvas.drawRect(0, 0, 100, 100, SkPaint());
506        });
507        EXPECT_EQ(-1, dl->projectionReceiveIndex)
508                << "no projection receiver should have been observed";
509    }
510    {
511        background->mutateStagingProperties().setProjectionReceiver(true);
512
513        // RECEIVER PRESENT
514        auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200,
515                    [&background](RecordingCanvas& canvas) {
516            canvas.drawRect(0, 0, 100, 100, SkPaint());
517            canvas.drawRenderNode(background.get());
518            canvas.drawRect(0, 0, 100, 100, SkPaint());
519        });
520
521        ASSERT_EQ(3u, dl->getOps().size()) << "Must be three ops";
522        auto op = dl->getOps()[1];
523        EXPECT_EQ(RecordedOpId::RenderNodeOp, op->opId);
524        EXPECT_EQ(1, dl->projectionReceiveIndex)
525                << "correct projection receiver not identified";
526
527        // verify the behavior works even though projection receiver hasn't been sync'd yet
528        EXPECT_TRUE(background->stagingProperties().isProjectionReceiver());
529        EXPECT_FALSE(background->properties().isProjectionReceiver());
530    }
531}
532
533TEST(RecordingCanvas, firstClipWillReplace) {
534    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
535        canvas.save(SaveFlags::MatrixClip);
536        // since no explicit clip set on canvas, this should be the one observed on op:
537        canvas.clipRect(-100, -100, 300, 300, SkRegion::kIntersect_Op);
538
539        SkPaint paint;
540        paint.setColor(SK_ColorWHITE);
541        canvas.drawRect(0, 0, 100, 100, paint);
542
543        canvas.restore();
544    });
545    ASSERT_EQ(1u, dl->getOps().size()) << "Must have one op";
546    // first clip must be preserved, even if it extends beyond canvas bounds
547    EXPECT_CLIP_RECT(Rect(-100, -100, 300, 300), dl->getOps()[0]->localClip);
548}
549
550TEST(RecordingCanvas, insertReorderBarrier) {
551    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
552        canvas.drawRect(0, 0, 400, 400, SkPaint());
553        canvas.insertReorderBarrier(true);
554        canvas.insertReorderBarrier(false);
555        canvas.insertReorderBarrier(false);
556        canvas.insertReorderBarrier(true);
557        canvas.drawRect(0, 0, 400, 400, SkPaint());
558        canvas.insertReorderBarrier(false);
559    });
560
561    auto chunks = dl->getChunks();
562    EXPECT_EQ(0u, chunks[0].beginOpIndex);
563    EXPECT_EQ(1u, chunks[0].endOpIndex);
564    EXPECT_FALSE(chunks[0].reorderChildren);
565
566    EXPECT_EQ(1u, chunks[1].beginOpIndex);
567    EXPECT_EQ(2u, chunks[1].endOpIndex);
568    EXPECT_TRUE(chunks[1].reorderChildren);
569}
570
571TEST(RecordingCanvas, refPaint) {
572    SkPaint paint;
573
574    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [&paint](RecordingCanvas& canvas) {
575        paint.setColor(SK_ColorBLUE);
576        // first two should use same paint
577        canvas.drawRect(0, 0, 200, 10, paint);
578        SkPaint paintCopy(paint);
579        canvas.drawRect(0, 10, 200, 20, paintCopy);
580
581        // only here do we use different paint ptr
582        paint.setColor(SK_ColorRED);
583        canvas.drawRect(0, 20, 200, 30, paint);
584    });
585    auto ops = dl->getOps();
586    ASSERT_EQ(3u, ops.size());
587
588    // first two are the same
589    EXPECT_NE(nullptr, ops[0]->paint);
590    EXPECT_NE(&paint, ops[0]->paint);
591    EXPECT_EQ(ops[0]->paint, ops[1]->paint);
592
593    // last is different, but still copied / non-null
594    EXPECT_NE(nullptr, ops[2]->paint);
595    EXPECT_NE(ops[0]->paint, ops[2]->paint);
596    EXPECT_NE(&paint, ops[2]->paint);
597}
598
599TEST(RecordingCanvas, refBitmapInShader_bitmapShader) {
600    SkBitmap bitmap = TestUtils::createSkBitmap(100, 100);
601    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [&bitmap](RecordingCanvas& canvas) {
602        SkPaint paint;
603        SkAutoTUnref<SkShader> shader(SkShader::CreateBitmapShader(bitmap,
604                SkShader::TileMode::kClamp_TileMode,
605                SkShader::TileMode::kClamp_TileMode));
606        paint.setShader(shader);
607        canvas.drawRoundRect(0, 0, 100, 100, 20.0f, 20.0f, paint);
608    });
609    auto& bitmaps = dl->getBitmapResources();
610    EXPECT_EQ(1u, bitmaps.size());
611}
612
613TEST(RecordingCanvas, refBitmapInShader_composeShader) {
614    SkBitmap bitmap = TestUtils::createSkBitmap(100, 100);
615    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [&bitmap](RecordingCanvas& canvas) {
616        SkPaint paint;
617        SkAutoTUnref<SkShader> shader1(SkShader::CreateBitmapShader(bitmap,
618                SkShader::TileMode::kClamp_TileMode,
619                SkShader::TileMode::kClamp_TileMode));
620
621        SkPoint center;
622        center.set(50, 50);
623        SkColor colors[2];
624        colors[0] = Color::Black;
625        colors[1] = Color::White;
626        SkAutoTUnref<SkShader> shader2(SkGradientShader::CreateRadial(center, 50, colors, nullptr, 2,
627                SkShader::TileMode::kRepeat_TileMode));
628
629        SkAutoTUnref<SkShader> composeShader(SkShader::CreateComposeShader(shader1, shader2,
630                SkXfermode::Mode::kMultiply_Mode));
631        paint.setShader(composeShader);
632        canvas.drawRoundRect(0, 0, 100, 100, 20.0f, 20.0f, paint);
633    });
634    auto& bitmaps = dl->getBitmapResources();
635    EXPECT_EQ(1u, bitmaps.size());
636}
637
638TEST(RecordingCanvas, drawText) {
639    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
640        Paint paint;
641        paint.setAntiAlias(true);
642        paint.setTextSize(20);
643        paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
644        std::unique_ptr<uint16_t[]> dst = TestUtils::asciiToUtf16("HELLO");
645        canvas.drawText(dst.get(), 0, 5, 5, 25, 25, kBidi_Force_LTR, paint, NULL);
646    });
647
648    int count = 0;
649    playbackOps(*dl, [&count](const RecordedOp& op) {
650        count++;
651        ASSERT_EQ(RecordedOpId::TextOp, op.opId);
652        EXPECT_EQ(nullptr, op.localClip);
653        EXPECT_TRUE(op.localMatrix.isIdentity());
654        EXPECT_TRUE(op.unmappedBounds.getHeight() >= 10);
655        EXPECT_TRUE(op.unmappedBounds.getWidth() >= 25);
656    });
657    ASSERT_EQ(1, count);
658}
659
660TEST(RecordingCanvas, drawTextInHighContrast) {
661    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
662        canvas.setHighContrastText(true);
663        Paint paint;
664        paint.setColor(SK_ColorWHITE);
665        paint.setAntiAlias(true);
666        paint.setTextSize(20);
667        paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
668        std::unique_ptr<uint16_t[]> dst = TestUtils::asciiToUtf16("HELLO");
669        canvas.drawText(dst.get(), 0, 5, 5, 25, 25, kBidi_Force_LTR, paint, NULL);
670    });
671
672    int count = 0;
673    playbackOps(*dl, [&count](const RecordedOp& op) {
674        ASSERT_EQ(RecordedOpId::TextOp, op.opId);
675        if (count++ == 0) {
676            EXPECT_EQ(SK_ColorBLACK, op.paint->getColor());
677            EXPECT_EQ(SkPaint::kStrokeAndFill_Style, op.paint->getStyle());
678        } else {
679            EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
680            EXPECT_EQ(SkPaint::kFill_Style, op.paint->getStyle());
681        }
682
683    });
684    ASSERT_EQ(2, count);
685}
686
687} // namespace uirenderer
688} // namespace android
689