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