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