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