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