RecordingCanvasTests.cpp revision 1713c7773cea83a9c1ba7e08c73434df33ca6f4b
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 <tests/common/TestUtils.h>
22#include <utils/Color.h>
23
24namespace android {
25namespace uirenderer {
26
27static void playbackOps(const DisplayList& displayList,
28        std::function<void(const RecordedOp&)> opReceiver) {
29    for (auto& chunk : displayList.getChunks()) {
30        for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
31            RecordedOp* op = displayList.getOps()[opIndex];
32            opReceiver(*op);
33        }
34    }
35}
36
37TEST(RecordingCanvas, emptyPlayback) {
38    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
39        canvas.save(SaveFlags::MatrixClip);
40        canvas.restore();
41    });
42    playbackOps(*dl, [](const RecordedOp& op) { ADD_FAILURE(); });
43}
44
45TEST(RecordingCanvas, clipRect) {
46    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) {
47        canvas.save(SaveFlags::MatrixClip);
48        canvas.clipRect(0, 0, 100, 100, SkRegion::kIntersect_Op);
49        canvas.drawRect(0, 0, 50, 50, SkPaint());
50        canvas.drawRect(50, 50, 100, 100, SkPaint());
51        canvas.restore();
52    });
53
54    ASSERT_EQ(2u, dl->getOps().size()) << "Must be exactly two ops";
55    EXPECT_CLIP_RECT(Rect(100, 100), dl->getOps()[0]->localClip);
56    EXPECT_CLIP_RECT(Rect(100, 100), dl->getOps()[1]->localClip);
57    EXPECT_EQ(dl->getOps()[0]->localClip, dl->getOps()[1]->localClip)
58            << "Clip should be serialized once";
59}
60
61TEST(RecordingCanvas, drawLines) {
62    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
63        SkPaint paint;
64        paint.setStrokeWidth(20); // doesn't affect recorded bounds - would be resolved at bake time
65        float points[] = { 0, 0, 20, 10, 30, 40, 90 }; // NB: only 1 valid line
66        canvas.drawLines(&points[0], 7, paint);
67    });
68
69    ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
70    auto op = dl->getOps()[0];
71    ASSERT_EQ(RecordedOpId::LinesOp, op->opId);
72    EXPECT_EQ(4, ((LinesOp*)op)->floatCount)
73            << "float count must be rounded down to closest multiple of 4";
74    EXPECT_EQ(Rect(20, 10), op->unmappedBounds)
75            << "unmapped bounds must be size of line, and not outset for stroke width";
76}
77
78TEST(RecordingCanvas, drawRect) {
79    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
80        canvas.drawRect(10, 20, 90, 180, SkPaint());
81    });
82
83    ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
84    auto op = *(dl->getOps()[0]);
85    ASSERT_EQ(RecordedOpId::RectOp, op.opId);
86    EXPECT_EQ(nullptr, op.localClip);
87    EXPECT_EQ(Rect(10, 20, 90, 180), op.unmappedBounds);
88}
89
90TEST(RecordingCanvas, drawText) {
91    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
92        SkPaint paint;
93        paint.setAntiAlias(true);
94        paint.setTextSize(20);
95        paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
96        TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
97    });
98
99    int count = 0;
100    playbackOps(*dl, [&count](const RecordedOp& op) {
101        count++;
102        ASSERT_EQ(RecordedOpId::TextOp, op.opId);
103        EXPECT_EQ(nullptr, op.localClip);
104        EXPECT_TRUE(op.localMatrix.isIdentity());
105        EXPECT_TRUE(op.unmappedBounds.contains(25, 15, 50, 25))
106                << "Op expected to be 25+ pixels wide, 10+ pixels tall";
107    });
108    ASSERT_EQ(1, count);
109}
110
111TEST(RecordingCanvas, drawText_strikeThruAndUnderline) {
112    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
113        SkPaint paint;
114        paint.setAntiAlias(true);
115        paint.setTextSize(20);
116        paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
117        for (int i = 0; i < 2; i++) {
118            for (int j = 0; j < 2; j++) {
119                paint.setUnderlineText(i != 0);
120                paint.setStrikeThruText(j != 0);
121                TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
122            }
123        }
124    });
125
126    auto ops = dl->getOps();
127    ASSERT_EQ(8u, ops.size());
128
129    int index = 0;
130    EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); // no underline or strikethrough
131
132    EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId);
133    EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough only
134
135    EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId);
136    EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline only
137
138    EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId);
139    EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline
140    EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough
141}
142
143TEST(RecordingCanvas, drawText_forceAlignLeft) {
144    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
145        SkPaint paint;
146        paint.setAntiAlias(true);
147        paint.setTextSize(20);
148        paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
149        paint.setTextAlign(SkPaint::kLeft_Align);
150        TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
151        paint.setTextAlign(SkPaint::kCenter_Align);
152        TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
153        paint.setTextAlign(SkPaint::kRight_Align);
154        TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
155    });
156
157    int count = 0;
158    float lastX = FLT_MAX;
159    playbackOps(*dl, [&count, &lastX](const RecordedOp& op) {
160        count++;
161        ASSERT_EQ(RecordedOpId::TextOp, op.opId);
162        EXPECT_EQ(SkPaint::kLeft_Align, op.paint->getTextAlign())
163                << "recorded drawText commands must force kLeft_Align on their paint";
164
165        // verify TestUtils alignment offsetting (TODO: move asserts to Canvas base class)
166        EXPECT_GT(lastX, ((const TextOp&)op).x)
167                << "x coordinate should reduce across each of the draw commands, from alignment";
168        lastX = ((const TextOp&)op).x;
169    });
170    ASSERT_EQ(3, count);
171}
172
173TEST(RecordingCanvas, drawColor) {
174    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
175        canvas.drawColor(Color::Black, SkXfermode::kSrcOver_Mode);
176    });
177
178    ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
179    auto op = *(dl->getOps()[0]);
180    EXPECT_EQ(RecordedOpId::RectOp, op.opId);
181    EXPECT_EQ(nullptr, op.localClip);
182    EXPECT_TRUE(op.unmappedBounds.contains(Rect(-1000, -1000, 1000, 1000)))
183            << "no clip, unmappedBounds should resolve to be much larger than DL bounds";
184}
185
186TEST(RecordingCanvas, backgroundAndImage) {
187    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
188        SkBitmap bitmap;
189        bitmap.setInfo(SkImageInfo::MakeUnknown(25, 25));
190        SkPaint paint;
191        paint.setColor(SK_ColorBLUE);
192
193        canvas.save(SaveFlags::MatrixClip);
194        {
195            // a background!
196            canvas.save(SaveFlags::MatrixClip);
197            canvas.drawRect(0, 0, 100, 200, paint);
198            canvas.restore();
199        }
200        {
201            // an image!
202            canvas.save(SaveFlags::MatrixClip);
203            canvas.translate(25, 25);
204            canvas.scale(2, 2);
205            canvas.drawBitmap(bitmap, 0, 0, nullptr);
206            canvas.restore();
207        }
208        canvas.restore();
209    });
210
211    int count = 0;
212    playbackOps(*dl, [&count](const RecordedOp& op) {
213        if (count == 0) {
214            ASSERT_EQ(RecordedOpId::RectOp, op.opId);
215            ASSERT_NE(nullptr, op.paint);
216            EXPECT_EQ(SK_ColorBLUE, op.paint->getColor());
217            EXPECT_EQ(Rect(100, 200), op.unmappedBounds);
218            EXPECT_EQ(nullptr, op.localClip);
219
220            Matrix4 expectedMatrix;
221            expectedMatrix.loadIdentity();
222            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
223        } else {
224            ASSERT_EQ(RecordedOpId::BitmapOp, op.opId);
225            EXPECT_EQ(nullptr, op.paint);
226            EXPECT_EQ(Rect(25, 25), op.unmappedBounds);
227            EXPECT_EQ(nullptr, op.localClip);
228
229            Matrix4 expectedMatrix;
230            expectedMatrix.loadTranslate(25, 25, 0);
231            expectedMatrix.scale(2, 2, 1);
232            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
233        }
234        count++;
235    });
236    ASSERT_EQ(2, count);
237}
238
239TEST(RecordingCanvas, saveLayer_simple) {
240    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
241        canvas.saveLayerAlpha(10, 20, 190, 180, 128, SaveFlags::ClipToLayer);
242        canvas.drawRect(10, 20, 190, 180, SkPaint());
243        canvas.restore();
244    });
245    int count = 0;
246    playbackOps(*dl, [&count](const RecordedOp& op) {
247        Matrix4 expectedMatrix;
248        switch(count++) {
249        case 0:
250            EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId);
251            EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
252            EXPECT_EQ(nullptr, op.localClip);
253            EXPECT_TRUE(op.localMatrix.isIdentity());
254            break;
255        case 1:
256            EXPECT_EQ(RecordedOpId::RectOp, op.opId);
257            EXPECT_CLIP_RECT(Rect(180, 160), op.localClip);
258            EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
259            expectedMatrix.loadTranslate(-10, -20, 0);
260            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
261            break;
262        case 2:
263            EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId);
264            // Don't bother asserting recording state data - it's not used
265            break;
266        default:
267            ADD_FAILURE();
268        }
269    });
270    EXPECT_EQ(3, count);
271}
272
273TEST(RecordingCanvas, saveLayer_missingRestore) {
274    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
275        canvas.saveLayerAlpha(0, 0, 200, 200, 128, SaveFlags::ClipToLayer);
276        canvas.drawRect(0, 0, 200, 200, SkPaint());
277        // Note: restore omitted, shouldn't result in unmatched save
278    });
279    int count = 0;
280    playbackOps(*dl, [&count](const RecordedOp& op) {
281        if (count++ == 2) {
282            EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId);
283        }
284    });
285    EXPECT_EQ(3, count) << "Missing a restore shouldn't result in an unmatched saveLayer";
286}
287
288TEST(RecordingCanvas, saveLayer_simpleUnclipped) {
289    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
290        canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SaveFlags::Flags)0); // unclipped
291        canvas.drawRect(10, 20, 190, 180, SkPaint());
292        canvas.restore();
293    });
294    int count = 0;
295    playbackOps(*dl, [&count](const RecordedOp& op) {
296        switch(count++) {
297        case 0:
298            EXPECT_EQ(RecordedOpId::BeginUnclippedLayerOp, 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_EQ(nullptr, op.localClip);
306            EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
307            EXPECT_TRUE(op.localMatrix.isIdentity());
308            break;
309        case 2:
310            EXPECT_EQ(RecordedOpId::EndUnclippedLayerOp, op.opId);
311            // Don't bother asserting recording state data - it's not used
312            break;
313        default:
314            ADD_FAILURE();
315        }
316    });
317    EXPECT_EQ(3, count);
318}
319
320TEST(RecordingCanvas, saveLayer_addClipFlag) {
321    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
322        canvas.save(SaveFlags::MatrixClip);
323        canvas.clipRect(10, 20, 190, 180, SkRegion::kIntersect_Op);
324        canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SaveFlags::Flags)0); // unclipped
325        canvas.drawRect(10, 20, 190, 180, SkPaint());
326        canvas.restore();
327        canvas.restore();
328    });
329    int count = 0;
330    playbackOps(*dl, [&count](const RecordedOp& op) {
331        if (count++ == 0) {
332            EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId)
333                    << "Clip + unclipped saveLayer should result in a clipped layer";
334        }
335    });
336    EXPECT_EQ(3, count);
337}
338
339TEST(RecordingCanvas, saveLayer_viewportCrop) {
340    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
341        // shouldn't matter, since saveLayer will clip to its bounds
342        canvas.clipRect(-1000, -1000, 1000, 1000, SkRegion::kReplace_Op);
343
344        canvas.saveLayerAlpha(100, 100, 300, 300, 128, SaveFlags::ClipToLayer);
345        canvas.drawRect(0, 0, 400, 400, SkPaint());
346        canvas.restore();
347    });
348    int count = 0;
349    playbackOps(*dl, [&count](const RecordedOp& op) {
350        if (count++ == 1) {
351            Matrix4 expectedMatrix;
352            EXPECT_EQ(RecordedOpId::RectOp, op.opId);
353            EXPECT_CLIP_RECT(Rect(100, 100), op.localClip) // Recorded clip rect should be
354            // intersection of viewport and saveLayer bounds, in layer space;
355            EXPECT_EQ(Rect(400, 400), op.unmappedBounds);
356            expectedMatrix.loadTranslate(-100, -100, 0);
357            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
358        }
359    });
360    EXPECT_EQ(3, count);
361}
362
363TEST(RecordingCanvas, saveLayer_rotateUnclipped) {
364    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
365        canvas.save(SaveFlags::MatrixClip);
366        canvas.translate(100, 100);
367        canvas.rotate(45);
368        canvas.translate(-50, -50);
369
370        canvas.saveLayerAlpha(0, 0, 100, 100, 128, SaveFlags::ClipToLayer);
371        canvas.drawRect(0, 0, 100, 100, SkPaint());
372        canvas.restore();
373
374        canvas.restore();
375    });
376    int count = 0;
377    playbackOps(*dl, [&count](const RecordedOp& op) {
378        if (count++ == 1) {
379            EXPECT_EQ(RecordedOpId::RectOp, op.opId);
380            EXPECT_CLIP_RECT(Rect(100, 100), op.localClip);
381            EXPECT_EQ(Rect(100, 100), op.unmappedBounds);
382            EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), op.localMatrix)
383                    << "Recorded op shouldn't see any canvas transform before the saveLayer";
384        }
385    });
386    EXPECT_EQ(3, count);
387}
388
389TEST(RecordingCanvas, saveLayer_rotateClipped) {
390    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
391        canvas.save(SaveFlags::MatrixClip);
392        canvas.translate(100, 100);
393        canvas.rotate(45);
394        canvas.translate(-200, -200);
395
396        // area of saveLayer will be clipped to parent viewport, so we ask for 400x400...
397        canvas.saveLayerAlpha(0, 0, 400, 400, 128, SaveFlags::ClipToLayer);
398        canvas.drawRect(0, 0, 400, 400, SkPaint());
399        canvas.restore();
400
401        canvas.restore();
402    });
403    int count = 0;
404    playbackOps(*dl, [&count](const RecordedOp& op) {
405        if (count++ == 1) {
406            Matrix4 expectedMatrix;
407            EXPECT_EQ(RecordedOpId::RectOp, op.opId);
408
409            // ...and get about 58.6, 58.6, 341.4 341.4, because the bounds are clipped by
410            // the parent 200x200 viewport, but prior to rotation
411            ASSERT_NE(nullptr, op.localClip);
412            ASSERT_EQ(ClipMode::Rectangle, op.localClip->mode);
413            // NOTE: this check relies on saveLayer altering the clip post-viewport init. This
414            // causes the clip to be recorded by contained draw commands, though it's not necessary
415            // since the same clip will be computed at draw time. If such a change is made, this
416            // check could be done at record time by querying the clip, or the clip could be altered
417            // slightly so that it is serialized.
418            EXPECT_RECT_APPROX_EQ(Rect(58.57864, 58.57864, 341.42136, 341.42136),
419                    (reinterpret_cast<const ClipRect*>(op.localClip))->rect);
420
421            EXPECT_EQ(Rect(400, 400), op.unmappedBounds);
422            expectedMatrix.loadIdentity();
423            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
424        }
425    });
426    EXPECT_EQ(3, count);
427}
428
429TEST(RecordingCanvas, drawRenderNode_projection) {
430    sp<RenderNode> background = TestUtils::createNode(50, 50, 150, 150,
431            [](RenderProperties& props, RecordingCanvas& canvas) {
432        SkPaint paint;
433        paint.setColor(SK_ColorWHITE);
434        canvas.drawRect(0, 0, 100, 100, paint);
435    });
436    {
437        background->mutateStagingProperties().setProjectionReceiver(false);
438
439        // NO RECEIVER PRESENT
440        auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200,
441                    [&background](RecordingCanvas& canvas) {
442            canvas.drawRect(0, 0, 100, 100, SkPaint());
443            canvas.drawRenderNode(background.get());
444            canvas.drawRect(0, 0, 100, 100, SkPaint());
445        });
446        EXPECT_EQ(-1, dl->projectionReceiveIndex)
447                << "no projection receiver should have been observed";
448    }
449    {
450        background->mutateStagingProperties().setProjectionReceiver(true);
451
452        // RECEIVER PRESENT
453        auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200,
454                    [&background](RecordingCanvas& canvas) {
455            canvas.drawRect(0, 0, 100, 100, SkPaint());
456            canvas.drawRenderNode(background.get());
457            canvas.drawRect(0, 0, 100, 100, SkPaint());
458        });
459
460        ASSERT_EQ(3u, dl->getOps().size()) << "Must be three ops";
461        auto op = dl->getOps()[1];
462        EXPECT_EQ(RecordedOpId::RenderNodeOp, op->opId);
463        EXPECT_EQ(1, dl->projectionReceiveIndex)
464                << "correct projection receiver not identified";
465
466        // verify the behavior works even though projection receiver hasn't been sync'd yet
467        EXPECT_TRUE(background->stagingProperties().isProjectionReceiver());
468        EXPECT_FALSE(background->properties().isProjectionReceiver());
469    }
470}
471
472TEST(RecordingCanvas, firstClipWillReplace) {
473    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
474        canvas.save(SaveFlags::MatrixClip);
475        // since no explicit clip set on canvas, this should be the one observed on op:
476        canvas.clipRect(-100, -100, 300, 300, SkRegion::kIntersect_Op);
477
478        SkPaint paint;
479        paint.setColor(SK_ColorWHITE);
480        canvas.drawRect(0, 0, 100, 100, paint);
481
482        canvas.restore();
483    });
484    ASSERT_EQ(1u, dl->getOps().size()) << "Must have one op";
485    // first clip must be preserved, even if it extends beyond canvas bounds
486    EXPECT_CLIP_RECT(Rect(-100, -100, 300, 300), dl->getOps()[0]->localClip);
487}
488
489TEST(RecordingCanvas, insertReorderBarrier) {
490    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
491        canvas.drawRect(0, 0, 400, 400, SkPaint());
492        canvas.insertReorderBarrier(true);
493        canvas.insertReorderBarrier(false);
494        canvas.insertReorderBarrier(false);
495        canvas.insertReorderBarrier(true);
496        canvas.drawRect(0, 0, 400, 400, SkPaint());
497        canvas.insertReorderBarrier(false);
498    });
499
500    auto chunks = dl->getChunks();
501    EXPECT_EQ(0u, chunks[0].beginOpIndex);
502    EXPECT_EQ(1u, chunks[0].endOpIndex);
503    EXPECT_FALSE(chunks[0].reorderChildren);
504
505    EXPECT_EQ(1u, chunks[1].beginOpIndex);
506    EXPECT_EQ(2u, chunks[1].endOpIndex);
507    EXPECT_TRUE(chunks[1].reorderChildren);
508}
509
510TEST(RecordingCanvas, refPaint) {
511    SkPaint paint;
512    paint.setAntiAlias(true);
513    paint.setTextSize(20);
514    paint.setTextAlign(SkPaint::kLeft_Align);
515    paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
516
517    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [&paint](RecordingCanvas& canvas) {
518        paint.setColor(SK_ColorBLUE);
519        // first three should use same paint
520        canvas.drawRect(0, 0, 200, 10, paint);
521        SkPaint paintCopy(paint);
522        canvas.drawRect(0, 10, 200, 20, paintCopy);
523        TestUtils::drawTextToCanvas(&canvas, "helloworld", paint, 50, 25);
524
525        // only here do we use different paint ptr
526        paint.setColor(SK_ColorRED);
527        canvas.drawRect(0, 20, 200, 30, paint);
528    });
529    auto ops = dl->getOps();
530    ASSERT_EQ(4u, ops.size());
531
532    // first three are the same
533    EXPECT_NE(nullptr, ops[0]->paint);
534    EXPECT_NE(&paint, ops[0]->paint);
535    EXPECT_EQ(ops[0]->paint, ops[1]->paint);
536    EXPECT_EQ(ops[0]->paint, ops[2]->paint);
537
538    // last is different, but still copied / non-null
539    EXPECT_NE(nullptr, ops[3]->paint);
540    EXPECT_NE(ops[0]->paint, ops[3]->paint);
541    EXPECT_NE(&paint, ops[3]->paint);
542}
543
544} // namespace uirenderer
545} // namespace android
546